简介
TCP 是一个’流’协议,所谓流,就是没有界限的一串数据. 大家可以想想河里的流水,是连成一片的.期间并没有分界线, TCP 底层并不了解上层业务数据的具体含义 ,它会根据 TCP 缓冲区的实际情况进行包得划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送 . 也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 拆包和粘包.
TCP 粘包/拆包问题说明
我们可以通过图解对 TCP 粘包和拆包进行说明.粘包问题示例图:
假设客户端分别发送了两个数据包 D1,D2 给服务端, 由于服务端一次读到的字节数是不确定的.故可能存在以下4种情况.
- 服务端分两次读取了两个独立的数据包. 分别是 D1,D2 ,没有粘包和拆包;
- 服务器一次收到两个数据包,D1和 D2粘在一起,被称为 TCP 的粘包;
- 服务器分两次读到两个数据包,第一次读到了完整的 D1包和D2的部分内容,第二次读取到了 D2包得剩余内容,这被称为 TCP 的拆包.
- 服务器分两次读到两个数据包,第一次读取到了 D1包得部分内容,第二次读取到了 D1包得剩余内容 D1_2和 D2包的整包.
如果此时服务端的 TCP 接收滑窗非常小, 而且数据包D1和 D2比较大,很有可能出现第五种可能,即 服务端分多次才能将 D1和 D2包接收完全. 期间发生多次拆包.
TCP 粘包/拆包发生的原因
问题产生的原因有三个,分别如下.
- 应用程序 write 写入的字节大小大于套接字接口发送缓冲区大小;
- 进行 MSS 大小的 TCP 分段;
- 以太网帧的 payload 大于 MTU 进行 IP 分片;
粘包问题的解决策略
由于底层的 TCP 无法理解上层业务数据,所以在底层是无法保证数据包不被拆分和重组的 , 这个问题只能通过上层的应用协议栈设计来解决,根据业界主流的协议的解决方案, 可以归纳如下:
- 消息定长, 例如每个报文的大小固定长度200字节,如果不够,空位补齐空格;
- 在包尾部添加回车换行符进行分割, 例如 FTP 协议;
- 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息具体长度)的字段,通常设计思路为消息头的第一个字段使用 int32 来表示消息的总长度;
- 更复杂的应用协议层;