什么是粘包、拆包?
对于什么是粘包、拆包问题,我想先举两个简单的应用场景:
客户端和服务器建立一个连接,客户端发送一条消息,客户端关闭与服务端的连接。
客户端和服务器简历一个连接,客户端连续发送两条消息,客户端关闭与服务端的连接。
对于第一种情况,服务端的处理流程可以是这样的:当客户端与服务端的连接建立成功之后,服务端不断读取客户端发送过来的数据,当客户端与服务端连接断开之后,服务端知道已经读完了一条消息,然后进行解码和后续处理...。对于第二种情况,如果按照上面相同的处理逻辑来处理,那就有问题了,我们来看看第二种情况下客户端发送的两条消息递交到服务端有可能出现的情况:
第一种情况:
服务端一共读到两个数据包,第一个包包含客户端发出的第一条消息的完整信息,第二个包包含客户端发出的第二条消息,那这种情况比较好处理,服务器只需要简单的从网络缓冲区去读就好了,第一次读到第一条消息的完整信息,消费完再从网络缓冲区将第二条完整消息读出来消费。
没有发生粘包、拆包示意图
第二种情况:
服务端一共就读到一个数据包,这个数据包包含客户端发出的两条消息的完整信息,这个时候基于之前逻辑实现的服务端就蒙了,因为服务端不知道第一条消息从哪儿结束和第二条消息从哪儿开始,这种情况其实是发生了TCP粘包。
TCP粘包示意图
第三种情况:
服务端一共收到了两个数据包,第一个数据包只包含了第一条消息的一部分,第一条消息的后半部分和第二条消息都在第二个数据包中,或者是第一个数据包包含了第一条消息的完整信息和第二条消息的一部分信息,第二个数据包包含了第二条消息的剩下部分,这种情况其实是发送了TCP拆,因为发生了一条消息被拆分在两个包里面发送了,同样上面的服务器逻辑对于这种情况是不好处理的。
TCP拆包示意图
为什么会发生TCP粘包、拆包呢?
发生TCP粘包、拆包主要是由于下面一些原因:
应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
接收方法不及时读取套接字缓冲区数据,这将发生粘包。
如何处理粘包、拆包问题?
知道了粘包、拆包问题及根源,那么如何处理粘包、拆包问题呢?TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法:
1、使用带消息头的协议、消息头存储消息开始标识及消息长度信息,服务端获取消息头的时候解析出消息长度,然后向后读取该长度的内容。
2、设置定长消息,服务端每次读取既定长度的内容作为一条完整消息。
3、设置消息边界,服务端从网络流中按消息编辑分离出消息内容。
4、……
如何基于Netty处理粘包、拆包问题?
netty给我提供了各种方式供我们使用。
1、DelimiterBasedFrameDecoder是基于消息边界方式进行粘包拆包处理的。
2、FixedLengthFrameDecoder是基于固定长度消息进行粘包拆包处理的。
3、LengthFieldBasedFrameDecoder是基于消息头指定消息长度进行粘包拆包处理的。
4、LineBasedFrameDecoder是基于行来进行消息粘包拆包处理的。
用户可以自行选择规则然后使用Netty提供的对应的Decoder来进行具有粘包、拆包处理功能的网络应用开发。
解决粘包问题
由于数据量小于缓存,所以发送信息时,会发生粘包情况。
下面是我们循环发送的短数据
for(int j=0;j<100;j++){
str="Hello Netty";
ch.writeAndFlush(str);
}
看下没拆包之前的接受打印
由输出可知,发送的数据粘在了一起,这显然不是我们想要的结果,这时,我们就使用
FixedLengthFrameDecoder
进行拆包。,下图是拆包后结果。解决拆包问题
由于数据量大于缓存,所以发送信息时,会发生拆包情况。
下面是我们发送一篇比较长的文章
String str="春江潮水连海平,海上明月共潮生。"
+" 滟滟随波千万里,何处春江无月明! "
+" 江流宛转绕芳甸,月照花林皆似霰;"
+" 空里流霜不觉飞,汀上白沙看不见。"
+" 江天一色无纤尘,皎皎空中孤月轮。"
+" 江畔何人初见月?江月何年初照人?"
+" 人生代代无穷已,江月年年望相似。"
+" 不知江月待何人,但见长江送流水。"
+" 白云一片去悠悠,青枫浦上不胜愁。"
+" 谁家今夜扁舟子?何处相思明月楼?"
+" 可怜楼上月徘徊,应照离人妆镜台。"
+" 玉户帘中卷不去,捣衣砧上拂还来。"
+" 此时相望不相闻,愿逐月华流照君。"
+" 鸿雁长飞光不度,鱼龙潜跃水成文。"
+" 昨夜闲潭梦落花,可怜春半不还家。"
+" 江水流春去欲尽,江潭落月复西斜。"
+" 斜月沉沉藏海雾,碣石潇湘无限路。"
+" 不知乘月几人归,落月摇情满江树。"
+" 噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。"
+" 西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。"
+" 黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。"
+" 问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。"
+" 蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。"
+" 其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。"
+" 所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。"
+" 蜀道之难,难于上青天,侧身西望长咨嗟!";
看下自动拆包输出
根据输出可以看到我们发送的字符串被拆成了二次接受,但这也不是我们想要的效果,使用
LineBasedFrameDecoder
进行粘包。项目地址https://github.com/DespairYoke/netty/tree/master/netty-pack