外观
一句话答案
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是数据报,像一个个独立的包裹,天然有边界。