Skip to content

TCP 的粘包和拆包能说说吗? ​

  • 标签:网络

🏛 业务架构师 ​

先立本质:TCP 是字节流协议,只保证字节顺序,不保存"消息"边界——"一条消息"是应用层的概念。于是出现两种现象:

  • 粘包:发送端两次 write 的数据(各 100 字节),接收端一次 read 收到 200 字节——Nagle 算法合并小包、接收缓冲区未及时读取,都会"粘";
  • 拆包:一条 500 字节的消息,接收端分两次 read 各拿到 250 字节——缓冲区不够、 MSS 分段。

纠正一个常见误区:这不是 TCP 的 bug,是"流"与"消息"的语义鸿沟——就像水流不分页,要分页得自己画线。

解法 = 应用层自定义边界(业务协议设计):

  • 定长协议:简单但浪费(短消息补齐),适合固定报文;
  • 分隔符:要处理正文转义——用户输入里带分隔符的坑真实存在过;
  • 长度域(业界标准):报文头写明 body 长度,Netty 的 LengthFieldBasedFrameDecoder 开箱即用——"头定界、体定长"是私有协议的默认答案,参数(长度域偏移/长度)就是双端契约。

业务视角的两个实战教训:

  1. 联调测不出、压测才爆发:功能测试消息稀疏,每包独立到达,粘拆包从不出现;一上量(高频连续发送)解析乱码报文刷屏。规约:私有协议上线前必须做粘拆包专项压测(连续高频发包验证重组正确性);
  2. 协议要版本化:头部带版本号——双端独立升级时新旧协议共存,没有版本号的协议第一次升级就是断网事故。

一句话:粘拆包考的是**"消息边界是业务契约"的意识**——协议设计评审与接口定义评审应当同级严肃。

🏗 系统架构师 ​

先说根源:TCP 是字节流协议,只保证字节有序到达,不维护"消息"边界。发送端三次 write 的数据,在网络与接收缓冲区里可能被任意"拼接/切分":

  • 粘包:两次 write 的数据被 Nagle 优化合并、或接收缓冲区里前后消息连着,一次 read 读到了两条消息(或一条半);
  • 拆包:一条大消息被 MSS/MTU 分片,或缓冲区一次 read 只读到前半段。

本质不是 TCP 的缺陷,而是**"流"与"消息"的语义鸿沟**——应用层必须自定义分帧协议。

四种分帧方案:

  1. 固定长度:每消息定长,浪费带宽,简单协议用;
  2. 分隔符:特殊字符结尾(\r\n、魔术字节),文本协议(Redis RESP、HTTP 头);
  3. 长度前缀(主流):| 长度字段 | payload |,接收端先读长度再收齐字节——自定义二进制协议标准做法(Dubbo、RocketMQ、Thrift);
  4. 更复杂的应用层结构:HTTP 的 Content-Length/chunked。

工程落地(Netty):LengthFieldBasedFrameDecoder(四参数适配各种长度字段位置)+ Protobuf 等序列化;必须设 maxFrameLength 防恶意超长帧打爆内存——分帧协议的安全边界,是我审网络代码的必查项。

关联认知:UDP 数据报有边界无此问题;WebSocket、HTTP/2 帧协议本质都是在流上定义帧边界的实例。

💻 全栈开发专家 ​

先纠正概念:TCP 没有"粘包/拆包 bug",它是字节流协议,本来就没有消息边界。"粘包/拆包"是应用层视角的现象:

  • 粘包:两次 send("A")、send("B"),接收端一次 recv 读到 "AB"——Nagle 优化合并小包、接收缓冲区读慢了都会这样;
  • 拆包:一次 send("大消息") 被拆成几段到达(超过 MSS 分段),要 recv 几次才凑齐。

根因:TCP 只保证字节流有序可靠,"一条消息"是应用层协议的概念,内核不知道。

解决方案(应用层自定义协议):

  1. 定长消息:每条固定 128B,不足补齐——浪费带宽,简单场景用;
  2. 分隔符:\r\n 或特殊字符,要处理转义;
  3. 长度前缀(TLV,最主流):
text
[length(4字节)] [body]
// 先读 4 字节长度 → 按 length 读 body → 一条完整消息
// Netty:new LengthFieldBasedFrameDecoder(65535, 0, 4, 0, 4)
  1. 更复杂的用魔数+版本+长度+类型的协议头,兼具校验和扩展。

我踩过的坑:

  • 长度字段用无符号 int,Java 的 int 是有符号,超 2GB 消息长度解析成负数直接异常;
  • 没限制最大长度,恶意包声明 10GB 长度直接把内存打爆——解码器必须配 maxFrameLength;
  • 调试时用 wireshark 或 netty 的 LoggingHandler 打 hex dump,两端对齐协议字节布局再联调,比口头约定靠谱。

一句话:TCP 给的是水管,消息的"箱子的规格"要应用层自己定义。