• TCP粘包和拆包


             TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。即面向流的通信是无消息保护边界的。
            图解TCP的粘包和拆包

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

        1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包

        2.服务端一次接受到了两个数据包,D1和D2粘合在一起,称之为TCP粘包

        3.服务端分两次读取到了数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这称之为TCP拆包

        4.服务端分两次读取到了数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余部分内容D1_2和完整的D2包。

          特别要注意的是,如果TCP的接受滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种情况,即服务端分多次才能将D1和D2包完全接受,期间发生多次拆包。

         

         粘包、拆包问题的解决方案:定义通信协议

         目前业界主流的协议(protocol)方案可以归纳如下:

         1 定长协议:假设我们规定每3个字节,表示一个有效报文。

         2.特殊字符分隔符协议:在包尾部增加回车或者空格符等特殊字符进行分割 。

         3.长度编码:将消息分为消息头和消息体,消息头中用一个int型数据(4字节),表示消息体长度的字段。在解析时,先读取内容长度Length,其值为实际消息体内容(Content)占用的字节数,之后必须读取到这么多字节的内容,才认为是一个完整的数据报文。

         

         下面我用长度编码方式解决TCP的粘包、拆包问题

         首先看一下不使用长度编码协议的时候会发生什么问题?

         Server端测试代码   

    public class MyServer {
    
        public static void main(String[] args) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).
                        childHandler(new MyServerInitializer());
    
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    
    public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {
        private int count;
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
            byte[] buffer = new byte[msg.readableBytes()];
            msg.readBytes(buffer);
            String message = new String(buffer, Charset.forName("utf-8"));
            System.out.println("服务端接收到的消息内容: " + message);
            System.out.println("服务端接收到的消息数量: " + (++this.count));
            ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(), Charset.forName("utf-8"));
            ctx.writeAndFlush(responseByteBuf);
        }
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            System.out.println(this);
    
            ChannelPipeline pipeline = ch.pipeline();
    
            pipeline.addLast(new MyServerHandler());
        }
    }

         Client端测试代码

    public class MyClient {
    
        public static void main(String[] args) throws Exception{
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).
                        handler(new MyClientInitializer());
    
                ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                eventLoopGroup.shutdownGracefully();
            }
        }
    }
    
    public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
        private int count;
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i = 0; i < 10; ++i) {
                ByteBuf buffer = Unpooled.copiedBuffer("sent from client ", Charset.forName("utf-8"));
                ctx.writeAndFlush(buffer);
            }
        }
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
            byte[] buffer = new byte[msg.readableBytes()];
            msg.readBytes(buffer);
    
            String message = new String(buffer, Charset.forName("utf-8"));
    
            System.out.println("客户端接收到的消息内容: " + message);
            System.out.println("客户端接收到的消息数量: " + (++this.count));
        }
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
    
            pipeline.addLast(new MyClientHandler());
        }
    }

              启动服务端,然后

              第一次启动客户端,服务端日志:

              第二次启动客户端,服务端日志:

              第三次启动客户端,服务端日志:

             从结果中可以看出发生了粘包问题。

     

             用长度编码方式解决TCP的粘包和拆包问题

             自定义协议CustomProtocol,解码器MyDecoder,编码器MyEncoder

    public class CustomProtocol {
    
        private int length;
    
        private byte[] content;
    
        public int getLength() {
            return length;
        }
    
        public void setLength(int length) {
            this.length = length;
        }
    
        public byte[] getContent() {
            return content;
        }
    
        public void setContent(byte[] content) {
            this.content = content;
        }
    }
    
    public class MyDecoder extends ReplayingDecoder<Void> {
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
            int length = in.readInt();
    
            byte[] content = new byte[length];
            in.readBytes(content);
    
            CustomProtocol personProtocol = new CustomProtocol();
            personProtocol.setLength(length);
            personProtocol.setContent(content);
    
            out.add(personProtocol);
        }
    }
    
    public class MyEncoder extends MessageToByteEncoder<CustomProtocol> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, CustomProtocol msg, ByteBuf out) throws Exception {
    
            out.writeInt(msg.getLength());
            out.writeBytes(msg.getContent());
        }
    }

              Server端测试代码   

    public class MyServer {
    
        public static void main(String[] args) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).
                        childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
    
                                pipeline.addLast(new MyDecoder());
                                pipeline.addLast(new MyEncoder());
    
                                pipeline.addLast(new MyClientHandler());
                            }
                        });
    
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    
    public class MyServerHandler extends SimpleChannelInboundHandler<CustomProtocol> {
    
        private int count;
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, CustomProtocol msg) throws Exception {
            int length = msg.getLength();
            byte[] content = msg.getContent();
    
            System.out.println("服务端接收到的数据:");
            System.out.println("长度: " + length);
            System.out.println("内容:" + new String(content, Charset.forName("utf-8")));
    
            System.out.println("服务端接收到的消息数量:" + (++this.count));
    
            String responseMessage = UUID.randomUUID().toString();
            int responseLength = responseMessage.getBytes("utf-8").length;
            byte[] responseContent = responseMessage.getBytes("utf-8");
    
            CustomProtocol personProtocol = new CustomProtocol();
            personProtocol.setLength(responseLength);
            personProtocol.setContent(responseContent);
    
            ctx.writeAndFlush(personProtocol);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

              Client端测试代码   

    public class MyClient {
    
        public static void main(String[] args) throws Exception {
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).
                        handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
    
                                pipeline.addLast(new MyDecoder());
                                pipeline.addLast(new MyEncoder());
    
                                pipeline.addLast(new MyClientHandler());
                            }
                        });
                ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
                channelFuture.channel().closeFuture().sync();
            } finally {
                eventLoopGroup.shutdownGracefully();
            }
        }
    }
    
    public class MyClientHandler extends SimpleChannelInboundHandler<CustomProtocol> {
    
        private int count;
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for (int i = 0; i < 10; ++i) {
                String messageToBeSent = "sent from client ";
                byte[] content = messageToBeSent.getBytes(Charset.forName("utf-8"));
                int length = messageToBeSent.getBytes(Charset.forName("utf-8")).length;
    
                CustomProtocol personProtocol = new CustomProtocol();
                personProtocol.setLength(length);
                personProtocol.setContent(content);
    
                ctx.writeAndFlush(personProtocol);
            }
        }
    
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, CustomProtocol msg) throws Exception {
            int length = msg.getLength();
            byte[] content = msg.getContent();
    
            System.out.println("客户端接收到的消息: ");
    
            System.out.println("长度: " + length);
            System.out.println("内容:" + new String(content, Charset.forName("utf-8")));
    
            System.out.println("客户端接受到的消息数量:" + (++this.count));
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

     测试结果:  

     结果分析:没有发生Tcp的粘包和拆包。                

          

  • 相关阅读:
    记录一下思源服务端的安装过程
    在Github上键政,然后被封号
    使用Aspnet_regiis加密web.config binzi
    策略模式
    Android Ril 分析
    20192422李俊洁 汇编语言程序设计1~4章学习笔记
    阿里云服务器如何配置开放端口
    关于DIFF插件的使用
    Python3 web开发中几种密码加密方式
    中间件安装——用Docker在CentOS7部署MongoDB服务
  • 原文地址:https://www.cnblogs.com/dyg0826/p/11335379.html
Copyright © 2020-2023  润新知