粘包:
多个应用层数据包在发送端被连续发出,接收端一次性接收到多个包的数据,分不出边界。
拆包:
一个完整的应用层消息被 TCP 拆成多段,接收端需要多次读取才能凑齐。
出现原因:
因为 TCP 是面向字节流的协议,只保障字节的顺序和可靠性,没有消息边界的概念。数据在底层可能被合并、拆分、延迟等,接收方必须自己判断一条消息何时结束。
如何解决呢?
解决粘包拆包问题
1 固定长度法
每条消息长度是固定的,例如每条消息都是 20 字节。
[20字节][20字节][20字节]
每次读20个字节就知道是一条消息了,这样简单;无需额外字段。
但是不支持变长消息,而且会有空间浪费,因为如果消息本身不够20字节,需要填充比如用0填充到20字节;
🧪 应用场景:
二进制协议(如 Modbus TCP);
简单嵌入式通信。
2 用字段表示长度法
在消息头部加一个字段,表示消息体的长度,接收端根据这个字段读取后续字节。
所以还需要在头部后面加个间隔,表示这是头部,下面是消息体。要不然混淆了。
[头部|总长度4字节|]
[消息内容]
这样支持变长消息;而且可靠,通用性强。
不过这样需要解析头部字段,结构复杂一点;还要解决字节序问题(大端/小端)。
🧪 应用场景:
HTTP (Content-Length)
gRPC、Thrift、Netty 框架协议;
自定义二进制协议。
补充
有些地方说http是靠特殊字符解决粘包拆包问题的,这个说法不对,这是http报文的内容:
在请求行、每一个头部行都会有空格+回车,以及整个请求头和请求主体之间会有空格+回车,但是请求主体后面却没有空格+回车;那么如果出现粘包的情况,前一个包的主体和后一个包的请求行之间没有空格+回车,这就还是有粘包的问题。
所以http不是靠特殊字符解决粘包拆包的问题,而是靠报文格式中明确的长度字段Content-Length来解决。
请求头和主体之间确实是靠空格+回车来划分,不过到底读到哪里是全部的主体,不是看空格+回车,而是这个长度字段。接收端读完 Content-Length 个字节就知道请求体结束了,下一个字节就是下一个 HTTP 报文的起点。
3 特殊字符定界法
使用某个特殊字符作为消息结束的标志(如换行符 \n、分号 ;、点 .)。
hello world\n
foo=123;\n
接收端不断读取数据,遇到分隔符就认为是完整一条消息。
这样简单易实现;直观、人类可读;适合文本协议。
但是如果消息体本身有特殊字符,比如说:
hello\n world\n
foo=123;\n
这样就会被当成3个消息了,所以消息体不要含有定界符,或需转义;
🧪应用层有些常见的协议确实是用特殊字符来解决粘包拆包问题的,比如
Redis 协议(RESP):\r\n 结尾
FTP、SMTP、POP3:\r\n 结尾
总结:
方法 | 是否支持变长 | 是否适合二进制 | 实现复杂度 | 粘包拆包处理 |
---|---|---|---|---|
固定长度 | ❌ 否 | ✅ 是 | ⭐⭐ | 简单,但不灵活 |
字段表示长度 | ✅ 是 | ✅ 是 | ⭐⭐⭐ | 主流方式 |
特殊字符定界 | ✅ 是 | ❌ 否 | ⭐⭐ | 文本协议好用 |