跳到主内容
快讯直播
AI智模界
AI 词典

粘包与拆包:TCP 是字节流,不是消息流

一句话定义

粘包与拆包,说的是这么一件事:TCP 只保证字节按顺序可靠到达,不保证“发一次”对应“收一次”。因此应用层的一条消息可能和别的消息粘在一起被一次读到(粘包),也可能只读到半条(拆包),接收方必须自己重新划出消息边界。

为什么一次 recv 会读到半个 JSON

打个比方:TCP 像一根水管,你往里先后倒进三杯水,对面接到的是一条连续的水流,从水面上看不出“这是第几杯”。程序调用三次 send(),写进去的是字节;对端调用 recv(),拿到的只是“此刻缓冲区里有多少字节”,可能是一条半消息,也可能是 0.3 条。

具体成因有几层:发送侧有发送缓冲区,Nagle 算法会把小包攒一攒再发;数据超过 MSS 会被切分成多个段;接收侧也有缓冲区,recv() 返回多少取决于当时到了多少字节,和你传入的缓冲区大小无关。所以“我每次读 4096 字节”这类写法并不能保证消息边界。

解决办法:长度前缀

最通用的做法是自己定一个帧头:每条消息先写固定长度(比如 4 字节)表示消息体的字节数,再写消息体。接收方维护一个 buffer:先攒够 4 字节读出长度 L,再攒够 L 字节切出一条完整消息交给业务代码,剩下的字节留在 buffer 里等下一轮。这样无论字节流被怎么切、怎么粘,都能原样还原。

其他常见方案是分隔符(HTTP 头部的空行、SSE 的 \n\n、按行传输)和定长帧。二进制数据首选长度前缀,因为分隔符本身可能出现在内容里。

分帧方式怎么划边界典型场景注意点
长度前缀先读长度,再读正文自定义 RPC、gRPC 消息长度字段要定好字节序和上限
分隔符遇到特定字节序列算一条HTTP 头、SSE、按行日志内容里出现分隔符要转义
定长帧每条固定 N 字节音视频、采集卡数据消息长度不灵活

和相邻概念的区别

UDP 是数据报,天然保留边界,一次发送对应一次接收,但可能丢、可能乱序;TCP 反过来,可靠有序,却把边界抹掉了。所以“粘包”不是 TCP 的缺陷,而是应用层协议少设计了分帧这一层。HTTP 的 Content-Length 与 chunked 编码、WebSocket 的帧头、gRPC 的长度前缀消息,本质都是同一件事的不同写法。

对从业者的实际意义

写 LLM 流式接口的客户端时,解析 SSE 必须按空行切事件,不能假设一次 read 就是一个事件;一个汉字可能被切在两个数据块中间,要用增量式 UTF-8 解码器。自研推理服务或内部 RPC 时,别把 JSON 直接裸拼在 TCP 上,加个长度前缀省掉后面无数次排查。线上看到“偶发 JSON 解析失败”“日志里半条消息”,先怀疑分帧,而不是网络抖动;TCP_NODELAY 只影响延迟,不解决边界问题。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。