面试知识库
进阶

TCP粘包与拆包#

一句话答案#

TCP 是字节流无消息边界导致粘包/拆包,解决:定长协议/分隔符/长度字段(Length+Body,推荐)。

核心要点

四次挥手过程:

客户端(主动关闭方)              服务端(被动关闭方)
   │── FIN(seq=u)─────────────→│  第1次:客户端请求关闭(我不再发数据了)
   │← ACK(ack=u+1)────────────│  第2次:服务端确认(我知道了,但我可能还有数据要发)
   │                              │  [服务端继续发送剩余数据...]
   │← FIN(seq=v)───────────────│  第3次:服务端也关闭(我也不发了)
   │── ACK(ack=v+1)────────────→│  第4次:客户端确认
   │                              │
   客户端等待 2MSL(TIME_WAIT)   服务端进入 CLOSED
plaintext

中间两次(ACK 和 FIN)为什么不能合并(通常情况下)?

  • 第2次 ACK:服务端立即确认”我收到你的 FIN 了”,防止客户端超时重发 FIN
  • 第3次 FIN:服务端可能还有未发完的数据,需要等数据发完后才能发 FIN
  • 中间存在半关闭状态:客户端不发数据了,但服务端可能还在发(单向数据传输)
  • 因此 ACK 和 FIN 之间有时间差,不能合并

什么情况下可以合并(三次挥手)?

  • 如果服务端在收到 FIN 时已经没有数据要发,可以同时发 ACK + FIN
  • Linux 内核的延迟 ACK 机制可能触发三次挥手
面试回答(2分钟版)

TCP 粘包和拆包的根本原因是 TCP 是面向字节流的协议,没有消息边界的概念。发送方写入的多条消息在 TCP 看来就是一连串字节,接收方从 socket 缓冲区读到的数据可能是多条消息合在一起(粘包),也可能是一条消息被拆成多次读取(拆包),还可能两者同时发生。产生的具体原因包括:Nagle 算法将小包合并发送、TCP 报文段大小受 MSS 限制、接收方读取速度和发送方写入速度不匹配等。解决粘包拆包本质上是在应用层定义消息边界,有三种常用方案:一是固定长度协议,每条消息定长不足补齐,简单但浪费空间;二是分隔符协议,用特殊字符如换行符标记消息结束,HTTP/1.1 的 Header 就用 CRLF 分隔;三是长度字段加内容的方式,消息头先写一个固定字节数的长度字段再跟实际数据,接收方先读长度再按长度读内容,这是最推荐的方案。Netty 提供了 LengthFieldBasedFrameDecoder 等现成的解码器来处理这个问题。UDP 是数据报协议天然有消息边界,不存在粘包问题。

追问与易错

追问方向:

  • “HTTP 怎么解决粘包?”→ HTTP/1.1 用 Content-Length 标明 Body 长度 或 Transfer-Encoding: chunked 分块传输,Header 用 CRLF 分隔
  • “Netty 怎么处理粘包?”→ 提供 LengthFieldBasedFrameDecoder(长度字段解码器)、DelimiterBasedFrameDecoder(分隔符)、FixedLengthFrameDecoder(定长)等开箱即用
  • “UDP 有粘包问题吗?”→ 没有,UDP 是数据报协议天然有消息边界,每次 recvfrom 读取的就是一个完整的数据报

易错点:

  • ❌ TCP 粘包是 TCP 的 bug——TCP 是字节流协议这是特性
  • ❌ UDP 也有粘包——UDP 是数据报协议有消息边界