zoukankan      html  css  js  c++  java
  • netty TCP 粘包拆包

    一 什么是TCP 粘包拆包

    TCP 协议是流数据,流数据的特点就是没有分界线;TCP 会将数据流 缓冲进 缓冲池,缓冲池对数据流进行推送;

    缓冲池对数据发送有可能完整的2个包回黏在一起发送,称为粘包

    缓冲池中有可能会对数据流进行拆包 发送数据,有可能数据包1中包含数据包2, 数据包2中包含数据包1;

    二 粘包拆包产生的原因

    1. 发送的数据大于TCP发送缓冲区剩余空间大小,TCP会发生拆包。
    2. 发送数据大于MSS(最大报文长度),TCP会在传输前进行拆包。
    3. 发送数据远小于TCP缓冲区的大小,TCP将多次写入缓冲区的数据一次发送,将会发生粘包。
    4. 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

    三 粘包拆包的解决方案

    • 在报文末尾增加换行符表明一条完整的消息,接收端可以根据换行符来判断消息是否完整。
    • 将消息分为消息头、消息体。可以在消息头中声明消息的长度,根据这个长度来获取报文(比如 808 协议)。
    • 规定好报文长度,不足的空位补齐,接收端取的时候按照长度截取

    四 模拟粘包

    我们在netty 入门应用中 改造 客户端的激活方法, 加入100个循环;

        /* *
         * @Author lsc
         * <p>触发回调 </p>
         * @Param [ctx]
         * @Return void
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i=0;i<100;i++){
                byte[] bytes = "关注公众号知识追寻者回复netty获取本教程源码".getBytes();
                // 创建节字缓冲区
                ByteBuf message = Unpooled.buffer(bytes.length);
                // 将数据写入缓冲区
                message.writeBytes(bytes);
                // 写入数据
                ctx.writeAndFlush(message);
            }
    
        }
    

    然后我们服务端就接收到的消息如下,发现发送的数据都连接在了一起,即发生了数据包的粘包情况;

    五 解决拆包粘包

    LineBasedFrameDecoder 解码器

    netty 中默认提高了多种节码器,和编码器;我们可以利用不同的编码,节码器进行解决粘包拆包的问题;

    比如 利用 LineBasedFrameDecoder 进行粘包问题;

    我们在服务端的通道初始化的时候加上编码器即可

     /**
         * @Author lsc
         * <p>通道初始化 </p>
         * @Param
         * @Return
         */
        private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
    
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 管道(Pipeline)持有某个通道的全部处理器
                ChannelPipeline pipeline = socketChannel.pipeline();
                // 解决粘包问题
                pipeline.addLast(new LineBasedFrameDecoder(1024));
                pipeline.addLast(new StringDecoder());
                // 添加处理器
                pipeline.addLast(new NettyServerHandler());
    
            }
        }
    

    当然我们在 客户端发送消息的时候就需要加上分割符号(知识追寻者这边以换行符号作为分割),要不然服务端没法接收消息

     /* *
         * @Author lsc
         * <p>触发回调 </p>
         * @Param [ctx]
         * @Return void
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i=0;i<100;i++){
                byte[] bytes = "关注公众号知识追寻者回复netty获取本教程源码
    ".getBytes();
                // 创建节字缓冲区
                ByteBuf message = Unpooled.buffer(bytes.length);
                // 将数据写入缓冲区
                message.writeBytes(bytes);
                // 写入数据
                ctx.writeAndFlush(message);
            }
    
        }
    

    最终打印效果如下

    如果想在客户端也解决此类问题,加上解码器即可;

    注:LineBasedFrameDecoder 支持 或者 进行解码;

    DelimiterBasedFrameDecoder 解码器

    DelimiterBasedFrameDecoder 解码器的应用和 LineBasedFrameDecoder 相似,不过 优点是我们可以自定义分割符号;

    比如我们修改服务端的解码器为 DelimiterBasedFrameDecoder ,指定分割符合为 %

     /**
         * @Author lsc
         * <p>通道初始化 </p>
         * @Param
         * @Return
         */
        private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
    
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 管道(Pipeline)持有某个通道的全部处理器
                ChannelPipeline pipeline = socketChannel.pipeline();
                pipeline.addLast(new DelimiterBasedFrameDecoder(10240, Unpooled.copiedBuffer("%".getBytes())));
                pipeline.addLast(new StringDecoder());
                // 添加处理器
                pipeline.addLast(new NettyServerHandler());
    
            }
        }
    

    我们在客户端发送数据的时候以 % 为结尾;

      /* *
         * @Author lsc
         * <p>触发回调 </p>
         * @Param [ctx]
         * @Return void
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i=0;i<100;i++){
               
                byte[] bytes = "关注公众号知识追寻者回复netty获取本教程源码%".getBytes();
                // 创建节字缓冲区
                ByteBuf message = Unpooled.buffer(bytes.length);
                // 将数据写入缓冲区
                message.writeBytes(bytes);
                // 写入数据
                ctx.writeAndFlush(message);
            }
    
        }
    

    FixedLengthFrameDecoder 解码器

    FixedLengthFrameDecoder是按固定的数据长度来进行解码,我们就不用关系分割符号的问题,所以这种节码器也非常实用;

    我们 需要精确的计算客户端发送的数据长度,然后在服务端解码器中配置,否则会出现乱码等情况;

    /**
         * @Author lsc
         * <p>通道初始化 </p>
         * @Param
         * @Return
         */
        private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
    
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 管道(Pipeline)持有某个通道的全部处理器
                ChannelPipeline pipeline = socketChannel.pipeline();
                pipeline.addLast(new FixedLengthFrameDecoder(63));
                pipeline.addLast(new StringDecoder());
                // 添加处理器
                pipeline.addLast(new NettyServerHandler());
    
            }
        }
    

    效果如下

    本套教程

  • 相关阅读:
    windows下多个python版本共存,删掉一个
    解决ModuleNotFoundError: No module named 'pip'问题
    Palindrome Linked List 综合了反转链表和快慢指针的解法
    30-Day Leetcoding Challenge Day9
    Longest Common Prefix 五种解法(JAVA)
    30-Day Leetcoding Challenge Day8
    30-Day Leetcoding Challenge Day7
    30-Day Leetcoding Challenge Day6
    30-Day Leetcoding Challenge Day2
    leetcode162 Find Peak Element
  • 原文地址:https://www.cnblogs.com/zszxz/p/14513146.html
Copyright © 2011-2022 走看看