2014年与宗宗一起去厦门测试软件接口的时候,与上级系统基于TCP方式通讯,数据量大时,经常通讯失败,检查日志发现是上级系统应该多次返回的数据一次性接收到了。
上网搜索了一下,才了解到TCP粘包的问题。摘录如下:
UDP丢包是因为数据包在传送过程中丢失了,而TCP是基于流式的发送,并且存在丢包重发机制,TCP是可靠连接而UDP是不可靠的。
正是由于TCP是流式传送的,也就是连接建立后可以一直不停的发送,并没有明确的边界定义,而用UDP发送的时候,是可以按照一个一个数据包去发送的,一个数据包就是一个明确的边界。
而TCP并没有数据包的概念,是完全流式的,他会开辟一个缓冲区,发送端往其中写入数据,每过一段时间就发送出去,然后接收端接收到这些数据,但是并不是说我发送了一次数据就肯定发送出去了,数据会在缓冲区中,有可能后续发送的数据和之前发送的数据同时存在缓冲区中随后一起发送,这就是粘包的一种形式。接收端也有产生粘包的情况,如果应用程序没有及时处理缓冲区中的数据,那么后续到达的数据会继续存放到缓冲区中,也就是2次接收的数据同时存在缓冲区中,下次取缓冲区的时候就会取出2次粘包后的数据,这是粘包的另外一种形式。还有其他许多形式,比如填充缓冲区到一半缓冲区满了直接发送了 但是其实那个包还没填充完全,这个就是不完整的粘包了,剩余数据会在下次发送的时候补上。
关于解决方法:如果你是连续的整个数据流,比如发送文件,那么完全不考虑粘包也无所谓,因为可以建立连接后发送,发送完毕后断开连接,整个数据流就是整个一个文件,无论数据从哪里切开都无所谓,整个拼接后依旧是整个一个文件的数据。
如果你发送的数据是多次通信,比如把一个目录下所有的文件名都发送过去,那么就不能当作一个整体发送了,必须对他们划分边界,有一个很简单的处理方法,就是采用"数据长度+实际数据"的格式来发送数据,这个"数据长度"的格式是固定宽度的,比如4字节,可以表示0~4GB的宽度了,足够用了,这个宽度说明了后续实际数据的宽度,这样你就可以把粘包后的数据按照正确的宽度取出来了。
每次都是取出4字节,随后按照正确的宽度取出后续部分的就OK了。
如果你的所有数据都是固定宽度的,比如不停的发送温度数据,每个都是1字节,那么宽度已知了,每次你都取出一个1字节就OK了,所以就不用发送宽度数据了。
当然你也可以按照建立连接断开连接来划分边界,每次发送数据都打开、关闭一次连接,不过对于频繁的小数据量是不可取的做法,因为开销太大,建立连接和关闭连接也是需要耗费网络流量的。
总而言之,粘包的情况是无法绝对避免的,因为网络环境是很复杂的,依赖发送和接收缓冲区的控制是不能保证100%的,只要在发送的数据中说明数据的宽度随后在接收部分按照这个宽度拆开就OK了,宽度全都是统一的,已知宽度的情况下拆开更加容易,连在发送端填入宽度数据都可以省去了。
本文用菊子曰发布