请选择 进入手机版 | 继续访问电脑版

【Netty】粘包问题分析,实现自定义协议拆包

[复制链接]
谢世民 发表于 2021-1-1 18:34:14 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
TCP 粘包拆包是指发送方发送的若干包数据到吸收方吸收时粘成一包或某个数据包被拆开吸收。如下图所示,Client 发了两个数据包 D1和 D2,但是 erver 端大概会收到如下几种情况的数据。

为什么出现粘包现象?
TCP 是面向毗连的,面向流的,提供高可靠性服务。 收发两头(客户端和服务器端)都要有成对的 Socket,因此, 发送端为了将多个发给吸收端的包,更有效的发给对方,使用了优化方法(Nagle 算法),将多次隔断较小且数据量小的数据,归并成一个大的数据块,然后进行封包。 这样做虽然提高了效率,但是吸收端就难于分辨出完整的数据包了,因为面向流的通信是无消息掩护边界的。
粘包示例

这里我们接纳【Netty】Socket 编程(C/S) --基于Netty的Server、Client示例(少注释)中的示例代码,将 NettyClientHandler 代码修改一下

channel 调用了 100次 writeAndFlush() ,照理说应该 Server 吸收100次,但实际是这样吗?我们来启动 Server 和 Client 来测试一下:

可以看到,并不是我们预期的那 Server 吸收100次消息,而是有的消息单条吸收,有的是二合一,有的是三合一,实在反面还显示的还有十合一。。。这就是粘包。
=> 办理方案–拆包

针对上面出现的粘包现象,我们有什么办理方案呢?答:拆包!

  • 格式化数据:每条数据有固定的格式(开始符、竣事符),比如 Just do it!&&(此中&&表现这条消息竣事了)。这种方法简朴易行,但选择开始符和竣事符的时候一定要注意,每条数据的内部一定不能出现开始符或竣事符。
  • 发送长度:发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前4位是数据的长度,应用层处置处罚时可以根据长度来判断每条数据的开始和竣事。
       
        这方法也可以明白成自界说协议,因为它不是直接发送原数据,我们自界说消息格式
       

我们一般推荐使用第二种方案,因为它的实现更加优雅稳妥。下面就来看看如何通过自界说协议办理上面的问题…
1.自界说协议

这个协议很简朴,就是在消息内容的底子上再添一个消息长度
  1. /** * 自界说协议包 */public class MyMessageProtocol {    // 一次发送包体内容    // 注:这里直接是字节数组,目的是适用于各种范例数据(String,int,Object...)    private byte[] content;    // 一次发送包体长度    // 注:对于差别的数据有差别长度,肯定是不能写死的,    //     如果没有特殊要求,实在也可以在 setContent 中设置 len    private int len;        public byte[] getContent() {        return content;    }    public void setContent(byte[] content) {        this.content = content;    }    public int getLen() {        return len;    }    public void setLen(int len) {        this.len = len;    }}
复制代码
2.自界说编码器

我们还需要自界说编码器,因为 Netty 没有能处置处罚我们自界说消息体 MyMessageProtocol 的编码器,所以我们需要自己实现一个编码器,去将我们的 MyMessageProtocol 消息体转换为二进制形式
  1. /*** 通过继承 MessageToByteEncoder 自界说编码器* 注:通过泛型指定编码何种范例消息*/public class MyMessageEncoder extends MessageToByteEncoder {    @Override    protected void encode(ChannelHandlerContext ctx, MyMessageProtocol msg, ByteBuf out) throws Exception {        System.out.println("MyMessageEncoder encode 方法被调用");        // 1.通过 Netty 的 writeBytes 写出消息        out.writeBytes(msg.getContent());        // 2.通过 Netty 的 writeInt 写出长度        out.writeInt(msg.getLen());    }}
复制代码
3.注册编码器



  注意,这里我们可以注册多个编码器,你想啊,我们 writeAndFlush() 发送的大概是我们自己的 MyMessageProtocol,也大概是 String,也大概是一个平凡 Object。
4.修改 ClientHandler

在发送消息的时候我们需要发送的是自界说协议的消息体
5.自界说解码器

上面我们界说了处置处罚 MyProtocol 的编码器,同样的,在 Server 收到消息后怎么拿到消息内容?
  1. /*** 通过继承 ByteToMessageDecoder 实现自界说解码器* 得到二进制字节码-> MyMessageProtocol 数据包(对象)*/public class MyMessageDecoder extends ByteToMessageDecoder {            // 用来记载把二进制消息读到哪了    int length = 0;    @Override    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {        System.out.println();        System.out.println("MyMessageDecoder decode 被调用");        //          System.out.println(in);        // 首先要读入消息长度,然后才气知道处置处罚反面的哪些字节        // int 为 4 字节        if(in.readableBytes() >= 4) {            if (length == 0){                length = in.readInt();            }            if (in.readableBytes() < length) {                System.out.println("当前可读数据不敷,继承等候。。");                return;            }                        byte[] content = new byte[length];            if (in.readableBytes() >= length){                in.readBytes(content);                // 构建 MyMessageProtocol对象,通报到下一个handler业务处置处罚                MyMessageProtocol messageProtocol = new MyMessageProtocol();                messageProtocol.setLen(length);                messageProtocol.setContent(content);                out.add(messageProtocol);            }            length = 0;        }    }}
复制代码
6.注册解码器

跟上面注册编码器一样,只有经过解码,我们才气拿到 MyMessageProtocol 对象


  注意,这里切记不能先把 StringDecoder 注册在 MyMessageDecoder 前面!因为 StringDecoder 的 decode() 就是直接把所有内容 toString(),而我们的想法是把所有消息都转成 MyMessageProtocol 的格式去吸收和处置处罚。而且,大多数情况下,解码器注册一个就够了。
7.修改 ServerHandler

既然收到的消息是 MyMessageProtocol 了,那我们的 Handler 肯定也要修改


  注:修改 SimpleChannelInboundHandler 的处置处罚范例为 MyMessageProtocol 十分重要!另外,我刚开始还实验再新写一个 Handler,不起作用…
OK,到此大功告成!下面我们就来测试一下把。
效果演示

依次启动 Server 和 Client(注:Client 还是发送100次Just do it!),效果如下:


整个项目布局如下:

  源码我放在 GitHub 上了,有兴趣的同学点击这里跳转…

来源:https://blog.csdn.net/weixin_43935927/article/details/112000518
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )