Netty 粘包/拆包应用案例及解决方案分析
熟悉tcp变成的可以知道,无论是客户端还是服务端,但我们读取或者发送消息的时候,都需要考虑tcp底层粘包/拆包机制,下面我们先看一下tcp 粘包/拆包和基础知识,然后模拟一个没有考虑tcp粘包/拆包导致功能异常的案例,最后,通过正确的例程来谈谈netty是如何实现的。
主要内容:
-
tcp粘包/拆包的基础知识
-
没考虑tcp粘包/拆包的问题案例
-
使用netty解决读半包问题
1、tcp粘包/拆包
tcp是个“流“协议,所谓流,就是没有界限的一串数据。tcp底层并不知道上层业务逻辑,它会根据tcp缓冲区的实际情况进行包的拆分,所以在业务上认为,一个完整的包可能会被拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的tcp粘包/拆包的问题。
2、tcp粘包/拆包发生的原因
问题产生的原因有三个:如下
-
应用程序write写入的字节大小大于套接口发送缓冲区大小;
-
进行mss大小的分段;
-
以太网帧的payload大于mtu进行ip分片;
备注:mtu是网络传输最大报文包。mss是网络传输数据最大值。
3、粘包问题的解决策略
由于底层tcp无法理解上层业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下:
-
消息定长,例如每个报文的大小长度200字节,如果不够,不空格;
-
在包尾增加回车换行符,例如ftp协议;
-
将消息分为消息头和消息体,消息头包含表示消息总长度的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;
-
更复杂的设计协议;
介绍完了tcp粘包/拆包的基础知识后,我们看一下netty是如何解决半包问题的,是如何使用netty的半包解码器来解决tcp粘包/拆包问题。
4、未考虑tcp粘包/拆包问题出现的功能异常
timeserver的改造(可以查看上一篇文章中的netty客户端-服务端的实现):
每读到一条消息后,就计数一次,然后发送应答消息给服务端。
timeclient端的改造:
运行结果(服务端接收指令):
the time server receive order : query time order
此处省略57行。。。。。。。
query time ord ; the counter is :1
the time server receive order :
此处省略43行。。。。。。。
query time order ; the counter is :2
运行结果(客户端接收响应):
now is : bad order
bad order
; the counter is : 1
原因分析:服务端运行结果表明它只接收到两条消息,第一条包含57条“query time order”指令,第二天包含了43条指令,总数100条,我们期望的也是100条,但是计数只有两条,所有发生tcp粘包,按照设计初衷,客户端应该收到100响应,但实际上只收到了1条,不难理解,客户端也发生了粘包,一条应答消息中包含两条“bad order”指令的消息。
5、通过linebasedframedecoder解决tcp粘包问题
为了解决tcp粘包/拆包导致的半包读写问题,netty默认提供了多种编解码器用于处理半包,这是其他nio框架和jdk原生的nio api不能匹敌的。
直接上代码
timeserver:
在原来的timeserverhandler之前增加了两个解码器:linebasedframedecoder、stringdecoder
timeserverhandler:
timeclient:
timeclienthandler:
支持tcp粘包的运行结果:
服务端:
the time server receive order : query time order ; the counter is :1
此处省略92条。。。。。。
the time server receive order : query time order ; the counter is :100
客户端:
now is : tue aug 21 15:15:21 cst 2018 ; the counter is : 1
此处省略92条。。。。。。
now is : tue aug 21 15:15:21 cst 2018 ; the counter is : 100
6、linebasedframedecoder、stringdecoder原来分析
linebasedframedecoder的工作原理是它依次遍历bytebuf中的可读字节,判断是否有“\n“或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标记的解码器,
stringdecoder非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的handler,
总结:linebasedframedecoder + stringdecoder组合就是按行切换的文本解码器,它被设计用来支持tcp的粘包、拆包。
疑问:
1、如果发送的消息不是以换行符结束的怎么办?
2、靠消息头中的长度字段来分包的怎么办?
这样的话是否需要自己写半包解码器,答案是否定的,netty 提供了多种支持 tcp粘包、拆包的解码器,用来满足需求,下面的文章中会详细介绍《分隔符解码器》《定长解码器》,因为它在项目中使用非常广泛,所以单独去分享这一知识点。
上一篇: django-1-框架介绍
下一篇: 洛谷P1481 魔族密码(LIS)