粘包、拆包发生原因
由于TCP协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将数据包发往服务器。但是如果发送的网络数据包太小,发送一次数据包的成本可能大大的超过了数据本身(4000%的消耗)这时候就需要启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器收到的数据就可能使多个数据流首位拼接在一起的, 服务器在接收到消息(数据流)的时候就无法将每个数据包区分开,这样产生了粘包;再有服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,也会造成粘包现象。
拆包产生的原因就简单的多:可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。
总结一下发生TCP粘包或拆包的原因,现列出常见的几点
1.要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2.待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3.要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4.接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
寻找答案
无论时拆包还是粘包我们需要解决的首要问题就是在设计上解决数据包的区分方法,在网上找到的比较好理解的方案
1.消息数据固定长度,但是浪费存储和网络资源
首先固定长度是我首先想到可行的方法(简单、容易理解、也容易调试和排错),缺点也很明显,长度设计就很尴尬。过大很容易造成带宽的浪费,太短了过长的数据会被截断。
2.使用分割符来区分包的界限
分割符这个就……
3.数据包的头部中增加数据包长度字段
最大的问题在于我们无法确定一个数据是否真的结束了。如果接收到的数据中断了(数据头部标记长度100实际只发送了50)然而我们并不知道,这将导致后面的数据全部无法正常读取,我们没有有办法去修正这个错误,甚至我们都无法察觉。
此外还有一种 RINGBUFFER 解决方案 , 这种方案本身过于复杂。因为成本原因(需要更多实践和测试)放弃了
确定思路
固定长度+包头说明
固定长度能让我们很好的避开数据中断了的问题,因为固定长度我们能很好的识别到数据的结尾。为每个数据包添加固定长度的包头用于描述是否是一个长数据的一部分(包括位置)。这样我们能很好的处理不同长度的数据,还能根据业务数据的实际情况调节单个数据包的长度。