- 八月
- 15
- load
- load
JAVA解决TCP/Socket粘包,断包问题 2017-08-15 22:21:05
JAVA的Socket通信,是基于TCP,而TCP,是个“流”协议,所谓“流”,就像一条河流,不管沙砾小鱼,拥挤一起,没有界限地流往各个方向,既然没有界限,那么我们可能会出现各方面的问题,如粘包、断包。
什么是粘包?
网上长篇大论的,我们通过一个小案例来理解:
客户端连续发送了两个包,分别发送的是:
{"id":1,"user":"sqsx"}
{"id":2,"user":"sqsx159"}
你预计收到的是:{"id":1,"user":"sqsx"}
可是你收到的可能是:{"id":1,"user":"sqsx"}{"id":2,"user":"sqsx159"}
为什么会粘包?
TCP:你的包太小了,又要马上发送,这都要分两次发性能很差的,我来帮你优化一下
怎么办?
既然TCP这么皮,那么我们定义一个固定长度的包头,专门存储接下来的包有多少字节,先提取到长度后再根据长度提取内容,可以保证即使粘包了也不会受到影响。
因为一个int是占用4个字节,所以,我们先占用4个字节用于存储内容的长度,那么可以接收4M的内容,只是发送文本基本够了,如果需要接收大文件,建议还是分片传输吧
根据策略修改后发送的内容如下:
16 00 00 00 7B 22 69 64 22 3A 31 2C 22 75 73 65 72 22 3A 22 73 71 73 78 22 7D
加粗部分,就是存储{"id":1,"user":"sqsx"}这些字符串的长度,而后面的,就是{"id":1,"user":"sqsx"}的二进制码
然后再发送一个
19 00 00 00 7B 22 69 64 22 3A 32 2C 22 75 73 65 72 22 3A 22 73 71 73 78 31 35 39 22 7D
上代码(注意,这不是最终解决方案):
客户端:
服务端:
关于断包
你以为这样就解决了?TCP既然这么皮,肯定不会就这样放过你。
根据上面的策略,发送短数据时不会出现问题,如果发送长数据,那么必须分次发送,为什么要分段?这和MSS和MTU有关(至于这是什么,并不是本文章说明的范围内),如果一次性发送那么大的东西,相当于在高速公路上无视所有车辆行驶。可能导致发送不完整。
(而且,只是发送短文本用得着以二进制发送么)
如果是长数据,或产生延迟就不会粘包,导致收到空数据替补,如以下案例:
发送:01 02 03 04 05 06
延迟1s,阻止粘包
发送:07 08 09 0A 0B 0C
接收4位:01 02 03 04
再接收4位:05 06 00 00
我的剧本可不是这么写的呀(我就想你粘包啊)
既然发送长数据或延迟导致不粘包,那么我们就手动“粘包”好了。主要步骤如下:
1.服务端必须要先知道客户端每次发送多少字节,并初始一个记录值
2.服务端接收数据时要记录接收了多少数据,每接收一次就减少一次记录值
3.当记录值不足以读取全部数据时,那么分两次读取,并把记录值重置
有点迷糊?来个小案例吧
记录A:客户端每次发送6个字节。
尝试读取4个字节,判断A > 4(6 > 4),可以读取到全部,允许一次性读取4个字节,得到01 02 03 04,并把记录A-所读取字节长度(6-4),A变为2
尝试读取4个字节,判断A > 4(2 > 4),不能读取到全部,先读取A(2)个字节,再读取B=4-A个字节(4-2),合并成一个记录,最后把A还原初始并减B(A=客户端每次发送长度-B)
结论:本来是读取了两次,实际读取了三次。
最后,放上双端的代码。
客户端:
服务端:
个人经验,如果你有更好的方法,欢迎留言指正
原创文章,转载请注明出处