粘包

由于TCP协议设计的原因,如果数据量少的话,则会整合多个数据包,然后再一起发送。如下所示,
我们使用一个Socket Client循环发送数据到服务端中,可以看到服务端只接收到一次数据

        try {
            Socket socket = new Socket("127.0.0.1", 9092);
            DataOutputStream out = new DataOutputStream(socket.getOutputStream());

            for (int i = 0; i < 100; i++) {
                System.out.println("run..");
                out.writeUTF("这是第 " + (i+1) + " 次发送的数据");
            }

            out.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

服务端handler接收并解析数据:

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接收数据:" + msg);
        for (Channel channel : sessionSet) {
            if (channel == ctx.channel()) {
                channel.writeAndFlush("[自己]:" + msg);
            } else {
                channel.writeAndFlush("[客户端]:" + msg);
            }
        }

        super.channelRead(ctx, msg);
    }

image

方案1:
如果客户端发送的数据是固定的,服务端可以直接固定读取n个字符串作为一次来使用,Netty在pipline中提供了FixedLengthFrameDecoder类来设置服务端每次读取多少个字节。

               .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            // 每次读取4个字节
                            ch.pipeline().addLast(new FixedLengthFrameDecoder(4));
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder( ));

                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })

客户端每次都会发送test字符串,而test字符串占用4个字节长度,我们只要每次固定读取客户端发送来的数据即可,运行如下:
image
缺点: 无法保证每次客户端发送古来的数据都是固定长度的。

方案2:
采用分隔符的方式来进行截取,例如客户端结尾以下划线分割来标识,服务端只要截取下划线之前的数据即可,这样做的弊端是,客户端发数据就不能发送带下划线的数据了。

               public void initChannel(SocketChannel ch) throws Exception {
                            // 解码最大长度为1024个字节,以下划线结尾
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("_".getBytes())));
                            ch.pipeline().addLast("decoder", new StringDecoder());
                            ch.pipeline().addLast("encoder", new StringEncoder( ));

                            ch.pipeline().addLast(new DiscardServerHandler());
                        }

执行结果如下:
image

方式3:
利用私有协议来解决粘包的问题,就像HTTP协议一样,将每部分代表特定的含义,比如我们将客户端发过来的信息的前四位代表客户端发送数据的长度,客户端发送数据不满4位是前几位补0,如下所示:

posted @   RainbowMagic  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示