IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> Netty粘包拆包,不注意也容易掉坑里。 -> 正文阅读

[网络协议]Netty粘包拆包,不注意也容易掉坑里。

最近项目中需要对接上游系统,同步客户信息的变更,采用TCP协议通信,Netty提供了很好对于NIO处理的解决方案,因而采用Netty接收变更数据。

引入相关依赖:

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

增加相关配置:

socket:
  port: 9000
  netty:
    bossThreadNum: 4
    workThreadNum: 16

创建服务端接收变跟数据

@Value("${socket.port}")
private int port;

@Value("${socket.netty.bossThreadNum}")
private int bossThreadNum;

@Value("${socket.netty.workThreadNum}")
private int workThreadNum;

@PostConstruct
public void init() throws Exception {
    NioEventLoopGroup bossGroup = new NioEventLoopGroup(bossThreadNum);
    NioEventLoopGroup workerGroup = new NioEventLoopGroup(workThreadNum);
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap
            .group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel channel) throws Exception {
                    ChannelPipeline pipeline = channel.pipeline();
                    pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                    pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                    // 具体处理业务的执行器
                    pipeline.addLast(new CustomerServerHandler(dispatch));
                }
            })
            .option(ChannelOption.SO_BACKLOG, DEFAULT_SO_BACKLOG)
            .childOption(ChannelOption.TCP_NODELAY, true)
            .childOption(ChannelOption.SO_KEEPALIVE, true);

    serverBootstrap.bind(new InetSocketAddress(port)).sync();
    logger.info("Socket Server started, bind port: " + port);
}

完成具体处理的业务逻辑,自测正常,也符合预期结果能接收和响应变更数据,但是到了线上测试环境,发现收到的报文不完全,只有一部分,显然是出现了粘包拆包。

回过头看一下定义的报文规则:8位长度头(不足8位数字补0)+ 具体的报文内容,显然这里并不能直接使用String的编解码器,使用String的编解码器报文长度超过1024就被截断了。回头看看报文格式,提供了长度头,想到Netty提供的4种用来处理粘包拆包的方案中,刚好就有长度解码,可以考虑通过LengthFieldBasedFrameDecoder解码器自定义长度就可以解决粘包拆包的问题。

说干就干,修改编解码器:

ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(4194304, 0, 8, 0, 8));
pipeline.addLast(new LengthFieldPrepender(8, true));
// 具体处理业务的执行器
pipeline.addLast(new CustomerServerHandler(dispatch));

但是修改后连报文都解析不出来了,Netty一直报TooLongFrameException,一开始以为是接收报文给的长度不够,调整了长度大小后还是一样出现问题。
在这里插入图片描述
后面发现是自己忽略了一个点,Netty提供的LengthFieldBasedFrameDecoder解码器确实是解决了自定义长度TCP粘包拆包的问题,但是这个长度解码器,是以数值形式来解析的,而实际上这个场景中的报文长度是字符串,所以要先把长度以字符串形式截取8位下来,转成整形再具体接收数据。Netty是没有提供字符串长度解码器,所以要自定义一个解码器。

自定义字符串长度解码器:

public class MsgDecoder extends ByteToMessageDecoder {

    private static final Logger logger = LoggerFactory.getLogger(MsgDecoder.class);

    private static final int HEAD_LENGTH = 8;

    @Override
    protected void decode(ChannelHandlerContext context, ByteBuf byteBuf, List<Object> list) throws Exception {

        // 消息小于接受的最小长度
        if (byteBuf.readableBytes() < HEAD_LENGTH) {
            return;
        }

        // 标记当前readIndex的位置
        byteBuf.markReaderIndex();

        // 读取报文头获取内容长度
        byte[] headBytes = new byte[HEAD_LENGTH];
        byteBuf.readBytes(headBytes);
        String headStr = new String(headBytes, StandardCharsets.UTF_8);
        logger.info("find head: {}", headStr);
        int contentLength = Integer.parseInt(headStr);

        // 如果读到的消息体长度小于传输的值,重置readerIndex并返回
        if (byteBuf.readableBytes() < contentLength) {
            byteBuf.resetReaderIndex();
            return;
        }

        // 读取报文内容
        byte[] contentBytes = new byte[contentLength];
        byteBuf.readBytes(contentBytes);
        String contentStr = new String(contentBytes, StandardCharsets.UTF_8);
        logger.info("esb receive msg: {}", contentStr);
        
        list.add(contentStr);
    }

}

将自定义的解码器加到管道中。

ChannelPipeline pipeline = channel.pipeline();
// 自定义解码器
pipeline.addLast(new MsgDecoder(macEnabled));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
// 具体处理业务的执行器
pipeline.addLast(new EsbServerHandler(dispatch));

之后便能正常接收到完整的报文了。

通过实现了字符串长度解码器,解析报文头部中的长度,进而读取整个报文内容,解决了粘包拆包的问题。从另一个方面也表明,对于一些问题,还是要知其然再去处理,不然容易给自己挖坑。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-22 12:42:59  更:2021-11-22 12:44:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 7:48:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码