美文网首页
Netty(三)TCP粘包/拆包

Netty(三)TCP粘包/拆包

作者: 刘泽田 | 来源:发表于2019-05-08 21:37 被阅读0次

        TCP是个“流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上任务,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题

    TCP粘包/拆包现象

        通过图解对TCP粘包和拆包问题说明:


    image.png

        假设客户端分别发送了两个数据包D1和D2给服务端,有序服务端一次读取到的字节数是不确定的,故可能存在以下4种情况:

    1. 服务端分两次读取到两个独立的数据包,分别是D1和D2,没有粘包和拆包;
    2. 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
    3. 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
    4. 服务端两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包.
      如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第5种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包.

    TCP粘包/拆包发生的原因

    1. 应用程序write写入的字节大小大于套接口发送缓冲区大小

    2. 进行MSS大小的TCP分段

    3. 以太网帧payload大于MTU进行IP分片.


      image.png

    粘包解决

    由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可归纳如下:

    1. 消息定长,例如每个豹纹的大小为固定长度200字节,如果不够,空位补空格;
    2. 在包尾增加回车换行符进行分割,例如FTP协议
    3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;

    异常案例

    继Netty(一)中的代码,修改NettyClientHandler:

    /**
     * @ClassName NettyClientHandler
     * @Description TODO
     * @Author liuzetian
     * @Date 2019/5/6 9:22 AM
     * @Version 1.0
     **/
    public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
            System.out.println("客户端收到消息:" + byteBuf.toString(CharsetUtil.UTF_8));
        }
    
        /**
         * 修改 循环发送100消息 预期服务端接收到100条消息
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ByteBuf byteBuf = null;
            String req = "Hello World\n";
            for(int i= 0;i<100;i++){
                byteBuf = Unpooled.copiedBuffer(req, CharsetUtil.UTF_8);
                ctx.writeAndFlush(byteBuf);
            }
    
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
    }
    

    服务端代码不变:

    /**
     * @ClassName Netty
     * @Description TODO
     * @Author liuzetian
     * @Date 2019/5/6 9:02 AM
     * @Version 1.0
     **/
    public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf in = (ByteBuf) msg;
            System.out.println( "服务器接收到消息:" + in.toString(CharsetUtil.UTF_8));
            ctx.write(in);
        }
    
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                    .addListener(ChannelFutureListener.CLOSE);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    

    打印结果:
    服务端:

    服务器接收到消息:Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldH
    服务器接收到消息:ello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World
    
    

    客户端

    客户端收到消息:Hello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello WorldH
    客户端收到消息:ello WorldHello WorldHello WorldHello WorldHello WorldHello WorldHello World
    
    

    有打印结果看服务端只收到了2条消息,服务端也发送了两条应答;即发生了粘包

    解决

    需要添加解码器,后面会介绍;如有错误,欢迎指正

    以上内容均摘自,《Netty权威指南》

    相关文章

      网友评论

          本文标题:Netty(三)TCP粘包/拆包

          本文链接:https://www.haomeiwen.com/subject/olypoqtx.html