Skip to content
进阶

一句话答案

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

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

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

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

  • 如果服务端在收到 FIN 时已经没有数据要发,可以同时发 ACK + FIN
  • Linux 内核的延迟 ACK 机制可能触发三次挥手
追问与易错

追问方向:

  • HTTP 怎么解决粘包?
  • Netty 怎么处理粘包?
  • UDP 有粘包问题吗?

易错点:

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

💡 记忆锚点

TCP字节流像水管里的水:你倒进去三杯(三条消息),流出来时可能混成一大杯(粘包)或半杯(拆包),因为水管不管你的"杯"边界。解决办法:固定杯子大小(定长)、加隔板(分隔符)、或先报水量再倒水(Length+Body,最推荐)。UDP是数据报,像一个个独立的包裹,天然有边界。