Springboot Netty 实现自定义协议

news2024/9/21 16:46:47

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程。

Springboot整合Netty

新建springboot项目,并在项目以来中导入netty包,用fastjson包处理jsonStr。

		<!-- netty -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.42.Final</version>
        </dependency>

        <!-- Json处理 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.16</version>
        </dependency>

创建netty相关配置信息文件

  1. yml配置文件——application.yml
# netty 配置
netty:
  # boss线程数量
  boss: 4
  # worker线程数量
  worker: 2
  # 连接超时时间
  timeout: 6000
  # 服务器主端口
  port: 18023
  # 服务器备用端口
  portSalve: 18026
  # 服务器地址
  host: 127.0.0.1
  1. netty配置实体类——NettyProperties与yml配置文件绑定
    通过@ConfigurationProperties(prefix = "netty")注解读取配置文件中的netty配置,通过反射注入值,需要在实体类中提供对应的setter和getter方法。

@ConfigurationProperties(prefix = "netty")对应的实体类属性名称不要求一定相同,只需保证“set”字符串拼接配置文件的属性和setter方法名相同即可。

@Configuration
@ConfigurationProperties(prefix = "netty")
public class NettyProperties {

    /**
     * boss线程数量
     */
    private Integer boss;

    /**
     * worker线程数量
     */
    private Integer worker;

    /**
     * 连接超时时间
     */
    private Integer timeout = 30000;

    /**
     * 服务器主端口
     */
    private Integer port = 18023;

    /**
     * 服务器备用端口
     */
    private Integer portSalve = 18026;

    /**
     * 服务器地址 默认为本地
     */
    private String host = "127.0.0.1";
	
	// setter、getter 。。。。
}
  1. 对netty进行配置,绑定netty相关配置设置
    Netty通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类。
@Configuration
@EnableConfigurationProperties
public class NettyConfig {
    final NettyProperties nettyProperties;

    public NettyConfig(NettyProperties nettyProperties) {
        this.nettyProperties = nettyProperties;
    }

    /**
     * boss线程池-进行客户端连接
     *
     * @return
     */
    @Bean
    public NioEventLoopGroup boosGroup() {
        return new NioEventLoopGroup(nettyProperties.getBoss());
    }

    /**
     * worker线程池-进行业务处理
     *
     * @return
     */
    @Bean
    public NioEventLoopGroup workerGroup() {
        return new NioEventLoopGroup(nettyProperties.getWorker());
    }

    /**
     * 服务端启动器,监听客户端连接
     *
     * @return
     */
    @Bean
    public ServerBootstrap serverBootstrap() {
        ServerBootstrap serverBootstrap = new ServerBootstrap()
                // 指定使用的线程组
                .group(boosGroup(), workerGroup())
                // 指定使用的通道
                .channel(NioServerSocketChannel.class)
                // 指定连接超时时间
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyProperties.getTimeout())
                // 指定worker处理器
                .childHandler(new NettyServerHandler());
        return serverBootstrap;
    }
}
  1. worker处理器,初始化通道以及配置对应管道的处理器
    自定义了##@##分割符,通过DelimiterBasedFrameDecoder来处理拆包沾包问题;
    通过MessageDecodeHandler将接收消息解码处理成对象实例;
    通过MessageEncodeHandler将发送消息增加分割符后并编码;
    最后通过ServerListenerHandler根据消息类型对应处理不同消息。
public class NettyServerHandler extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 数据分割符
        String delimiterStr = "##@##";
        ByteBuf delimiter = Unpooled.copiedBuffer(delimiterStr.getBytes());
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 使用自定义处理拆包/沾包,并且每次查找的最大长度为1024字节
        pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
        // 将上一步解码后的数据转码为Message实例
        pipeline.addLast(new MessageDecodeHandler());
        // 对发送客户端的数据进行编码,并添加数据分隔符
        pipeline.addLast(new MessageEncodeHandler(delimiterStr));
        // 对数据进行最终处理
        pipeline.addLast(new ServerListenerHandler());
    }
}
  1. 数据解码
    数据解码和编码都采用UTF8格式
public class MessageDecodeHandler extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
        ByteBuf frame = in.retainedDuplicate();
        final String content = frame.toString(CharsetUtil.UTF_8);
        Message message = new Message(content);
        list.add(message);
        in.skipBytes(in.readableBytes());
    }
}
  1. 数据解码转换的实例
    Message类用于承载消息、转JsonString

public class Message {
    /**
     * 数据长度
     */
    private Integer len;

    /**
     * 接收的通讯数据body
     */
    private String content;

    /**
     * 消息类型
     */
    private Integer msgType;

    public Message(Object object) {
        String str = object.toString();
        JSONObject jsonObject = JSONObject.parseObject(str);
        msgType = Integer.valueOf(jsonObject.getString("msg_type"));
        content = jsonObject.getString("body");
        len = str.length();
    }

    public String toJsonString() {
        return "{" +
                "\"msg_type\": " + msgType + ",\n" +
                "\"body\": " + content +
                "}";
    }
	// setter、getter 。。。。
}
  1. 数据编码
    netty服务端回复消息时,对消息转JsonString增加分割符,并进行编码。
public class MessageEncodeHandler extends MessageToByteEncoder<Message> {
    // 数据分割符
    String delimiter;

    public MessageEncodeHandler(String delimiter) {
        this.delimiter = delimiter;
    }

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Message message, ByteBuf out) throws Exception {
        out.writeBytes((message.toJsonString() + delimiter).getBytes(CharsetUtil.UTF_8));
    }
}
  1. 数据处理器,针对不同类型数据分类处理
    在处理不同接收数据时使用了枚举类型,在使用switch时可以做下处理,具体参考代码,这里只演示如何操作,并没实现数据处理业务类。

public class ServerListenerHandler extends SimpleChannelInboundHandler<Message> {
    private static final Logger log = LoggerFactory.getLogger(ServerListenerHandler.class);

    /**
     * 设备接入连接时处理
     *
     * @param ctx
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        log.info("有新的连接:[{}]", ctx.channel().id().asLongText());
    }

    /**
     * 数据处理
     *
     * @param ctx
     * @param msg
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Message msg) {
        // 获取消息实例中的消息体
        String content = msg.getContent();
        // 对不同消息类型进行处理
        MessageEnum type = MessageEnum.getStructureEnum(msg);
        switch (type) {
            case CONNECT:
                // TODO 心跳消息处理
            case STATE:
                // TODO 设备状态
            default:
                System.out.println(type.content + "消息内容" + content);
        }
    }

    /**
     * 设备下线处理
     *
     * @param ctx
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        log.info("设备下线了:{}", ctx.channel().id().asLongText());
    }

    /**
     * 设备连接异常处理
     *
     * @param ctx
     * @param cause
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 打印异常
        log.info("异常:{}", cause.getMessage());
        // 关闭连接
        ctx.close();
    }
}
  1. 数据类型枚举类

public enum MessageEnum {
    CONNECT(1, "心跳消息"),
    STATE(2, "设备状态");

    public final Integer type;
    public final String content;

    MessageEnum(Integer type, String content) {
        this.type = type;
        this.content = content;
    }

    // case中判断使用
    public static MessageEnum getStructureEnum(Message msg) {
        Integer type = Optional.ofNullable(msg)
                .map(Message::getMsgType)
                .orElse(0);
        if (type == 0) {
            return null;
        } else {
            List<MessageEnum> objectEnums = Arrays.stream(MessageEnum.values())
                    .filter((item) -> item.getType() == type)
                    .distinct()
                    .collect(Collectors.toList());
            if (objectEnums.size() > 0) {
                return objectEnums.get(0);
            }
            return null;
        }
    }
	// setter、getter。。。。
}

到此Netty整个配置已经完成,但如果要跟随springboot一起启动,仍需要做一些配置。

  1. netty启动类配置
@Component
public class NettyServerBoot {
    private static final Logger log = LoggerFactory.getLogger(NettyServerBoot.class);
    @Resource
    NioEventLoopGroup boosGroup;
    @Resource
    NioEventLoopGroup workerGroup;
    final ServerBootstrap serverBootstrap;
    final NettyProperties nettyProperties;

    public NettyServerBoot(ServerBootstrap serverBootstrap, NettyProperties nettyProperties) {
        this.serverBootstrap = serverBootstrap;
        this.nettyProperties = nettyProperties;
    }


    /**
     * 启动netty
     *
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        // 绑定端口启动
        serverBootstrap.bind(nettyProperties.getPort()).sync();
        // 备用端口
        serverBootstrap.bind(nettyProperties.getPortSalve()).sync();
        log.info("启动Netty: {},{}", nettyProperties.getPort(), nettyProperties.getPortSalve());
    }

    /**
     * 关闭netty
     */
    @PreDestroy
    public void close() {
        log.info("关闭Netty");
        boosGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

增加NettyServerBoot配置后,启动application时,netty服务端会跟随一起启动。
在这里插入图片描述
同时,在springboot关闭前,会先销毁netty服务。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《编程思维与实践》1039.字符组合

《编程思维与实践》1039.字符组合 题目 思路 先将字符串去重排序(保证每个组合中的字符按字典序),然后枚举出所有组合的情形,最后再进行字典序排序即可. 其中字符串的去重排序可以利用ASCII码值进行桶排序,关键在于如何枚举所有组合的情形. 每个位置有两种可能(选或不选),但至…

MongoDB 聚合管道的输出结果到集合($out)及合并结果到集合($merge)

上一篇文章&#xff0c;我们介绍了使用聚合管道完成文档之间的关联查询、以及如果将两个管道中的文档进行合并&#xff0c;如果需要进一步了解可以参考&#xff1a;MongoDB 聚合管道的文档关联查询($lookup)及管道合并($unionWith)https://blog.csdn.net/m1729339749/article/d…

ES索引管理

ES官方博客&#xff1a;https://elasticstack.blog.csdn.net/?typeblog 一、rolloverAPI https://elasticstack.blog.csdn.net/article/details/102728987 1.1 rollover命令 POST /log_alias/_rollover { "conditions":{ "max_age":"…

Node 09 MongoDB的使用

MongoDB 简介 Mongodb 是什么 MongoDB 是一个基于分布式文件存储的数据库&#xff0c;官方地址 https://www.mongodb.com/ 数据库是什么 数据库&#xff08;DataBase&#xff09;是按照数据结构来组织、存储和管理数据的 应用程序 数据库的作用 数据库的主要作用就是 管理…

jsp家庭农场投入品信息管理系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 jsp家庭农场投入品信息管理系统是一套完善的java web信息管理系统 serlvet dao bean 开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发…

如何理解PCB布线3W规则

我们平时在PCB布线的时候&#xff0c;对于比较重要的信号都要做特殊处理&#xff0c;比如包地或者时“3W”&#xff0c;所谓3w指的是线与线之间的间距要满足三倍的线宽&#xff0c;那么我们怎么理解这个3W原则呢&#xff0c;他是如何降低信号之间的串扰的呢&#xff1f; 我们要…

连网介质及设备详解

文章目录 一、网卡1. 有线网卡2. 无线网卡3. 光纤网卡 二、网线1. 双绞线2. 光缆 三、交换机1. 什么是交换机2. 交换机分类 一、网卡 网卡分为三类&#xff1a;有线网卡、无线网卡、光纤网卡 1. 有线网卡 多数台式计算机自带&#xff0c;采用 RJ-45 制式接口 通过双绞线传输…

Hive2安装Tez计算引擎

一、Tez介绍 ApacheTEZ项目旨在构建一个应用程序框架&#xff0c;该框架允许使用复杂的有向无环图来处理数据。 它当前构建在Apache Hadoop YARN之上。 Tez的2个主要设计主题是&#xff1a; 通过以下方式增强最终用户的能力&#xff1a; 富有表现力的数据流定义API 灵活的输入…

ICV:车载毫米波雷达中国市场有望在2025年实现30亿美元的市场规模

近日&#xff0c;专注于前沿科技领域的国际咨询机构ICV发布了全球车载毫米波雷达的市场研究报告&#xff0c;汽车毫米波&#xff08;mmWave&#xff09;雷达基于频率为77 GHz的电磁波&#xff0c;具有高精度和准确性&#xff0c;可用于目标检测。这种技术有着广泛的应用&#x…

Dubbo面试大全

Dubbo面试题 1.Dubbo 是什么&#xff1f; Dubbo是阿里巴巴开源的分布式&#xff0c;高性能的透明化的RPC服务框架&#xff0c;提供服务自动注册&#xff0c;自动发现等高效服务治理方案&#xff0c;可以和Spring框架可以无缝衔接。 2.Dubbo的由来 你们的项目为什么会使用Du…

论文阅读 关联规则挖掘综述

这是一篇关联规则挖掘的综述&#xff0c;也记录下自己的心得笔记 A comprehensive review of visualization methods for association rule mining: Taxonomy, Challenges, Open problems and Future ideas 文章目录 摘要1、介绍2、关联规则挖掘是个小东西2.1、数值关联规则挖…

AGI智能新时代,技术驱动营销数字化重组|数说故事D3峰会盛况

万物向新&#xff0c;数字重组。 3月29日&#xff0c;以「REMIX——重组数字未来&#xff0c;创享无限可能」为主题的数说故事第六届D3智能营销峰会在广州白云国际会议中心圆满举办。本届峰会由数说故事携手GDMS联合主办&#xff0c;数说故事作为专场合作伙伴&#xff0c;与50…

php通过cURL爬取数据的方法(ajax接口、cookie伪造爬取、文件头信息)

php通过curl爬取数据 一、请求流程1.CURL请求步骤2.使用CURL发送GET请求3.使用CURL发送POST请求 二、实战&#xff1a;curl通过ajax接口API爬取数据1.接口分析2.抓取分析3.构建curl4.结果呈现 三、实战&#xff1a;curl添加cookie伪造登陆爬取数据1.注册用户&#xff0c;并记录…

【老王读SpringMVC-4】请求参数是如何绑定到Controller method参数对象上的?

前面我们分析了&#xff0c;如果我们自己要实现 spring mvc 框架的话&#xff0c;大致需要实现如下功能&#xff1a; 0、将 url 与 Controller method 的对应关系进行注册1、通过请求的 url 找到 Controller method (即 url 与 Controller method 的映射)2、将请求参数进行绑定…

FPGA时序约束(四)主时钟和虚拟时钟的约束

系列文章目录 FPGA时序约束&#xff08;一&#xff09;基本概念入门及简单语法 FPGA时序约束&#xff08;二&#xff09;利用Quartus18对Altera进行时序约束 FPGA时序约束&#xff08;三&#xff09;时序约束基本路径的深入分析 文章目录 系列文章目录前言主时钟约束跨时钟域…

计算机网络【2】 子网掩码

学习大佬记下的笔记 https://zhuanlan.zhihu.com/p/163119376 "子网"掩码&#xff0c;顾名思义&#xff0c;它就是拿来划分子网的&#xff0c;更准确的说&#xff0c;划分子网的同时&#xff0c;还能通过它知道主机在子网里面的具体ip的具体地址。 子网掩码只有一个…

聊聊「低代码」的实践之路

区块链、低代码、元宇宙、AI智能&#xff1b; 01 【先来说说背景】 这个概念由来已久&#xff0c;但是在国内兴起&#xff0c;是最近几年&#xff1b; 低代码即「Low-Code」&#xff1b; 指提供可视化开发环境&#xff0c;可以用来创建和管理软件应用&#xff1b; 简单的说…

中英文切换,vue项目国际化使用教程(国际化使用详细,i18n国际化)

简述&#xff1a;在工作中&#xff0c;我们难免会遇到把文字切换成外语的需求&#xff0c;这里来记录下如何在项目中点击切换成英语&#xff0c;这里会用到i18n&#xff0c;它是一个支持国际化功能的插件&#xff0c;这里来分享下它的使用过程。 1、首先&#xff0c;我们需要在…

idea使用 ( 四 ) 插件

5.插件 5.1.idea数据库连接 5.1.1.打开配置界面 5.1.2.选择MySQL 点击 号 > Data Source > MySQL 5.1.3.配置数据库驱动jar 先在左边选择 MySQL 再点击 号 > Custom JARs… 在 弹出的窗口中选择 已经存在的 jar位置 就导入 MySQL 的驱动文件 5.1.4.配置连库…

记录一次在x86 软件中使用dpdk 的历程(Makefile gcc改成g++)

我们一台服务器上原本是用grub下预留内存的方式, 然后把物理地址在板卡上的配置文件中传给L1. 但是在客户的环境上服务器windriver上不是能预留内存的. 所以服务器上需要在testMxx程序中用dpdk的方式分配出内存, 然后, 把物理地址通过sdp虚拟的网口&#xff0c; 用socket 传…