高 进阶
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) 服务端进入 CLOSEDplaintext中间两次(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 是数据报协议有消息边界