说说 TCP 的四次挥手?
- 标签:网络
🏛 业务架构师
四次挥手:主动方发 FIN(我不再发了)→ 被动方 ACK(知道了,但我可能还有数据没发完)→ 被动方数据发完再发 FIN → 主动方 ACK 并进入 TIME_WAIT(等 2MSL 后才真正关闭)。
之所以四次:TCP 全双工,两个方向要独立关闭——被动方的 ACK 和 FIN 不能合并,因为它收到 FIN 时可能还有数据要发(这就是应用层"半关闭"的由来)。
业务排查视角的两个高频状态才是重点:
- 大量 CLOSE_WAIT:说明对端已经关了连接,你的应用代码迟迟不调 close——连接泄漏,典型根因是异常分支没释放资源。这是应用代码 bug 的信号灯,我在巡检时看到 CLOSE_WAIT 数千个,基本可以直接开 bug 单;
- 大量 TIME_WAIT:主动关闭方的正常等待,但高并发短连接下会耗尽端口(尤其客户端)。解法是长连接复用(连接池、keep-alive)、
tcp_tw_reuse(客户端侧),而不是盲目调小 2MSL——等 2MSL 是为了让旧连接的迟到报文自然消亡、并保证最后的 ACK 能重传,粗暴缩短可能让新连接收到旧连接的幽灵报文。
业务引申:连接生命周期管理的成本意识——内部服务间用连接池化的 RPC 长连接,对外爬虫/调用方注意端口与 TIME_WAIT 水位。四次挥手是协议的收尾,连接管理的纪律才是业务的收尾。
🏗 系统架构师
四次挥手是 TCP 全双工连接的两个方向各自独立关闭的过程:
- FIN:主动方发 FIN,进入 FIN_WAIT_1("我不再发数据了");
- ACK:被动方回 ACK,进入 CLOSE_WAIT(可能还有数据要发完);
- FIN:被动方数据发完后发 FIN,进入 LAST_ACK;
- ACK:主动方回 ACK,进入 TIME_WAIT,等 2MSL 后关闭;被动方收到 ACK 即关闭。
为什么四次而不是三次:因为全双工。握手时 SYN+ACK 可以合并(服务端此时无数据依赖),挥手时被动方"收到 FIN"和"我也能关"是两个时刻——中间还要把剩余数据发完,所以 ACK 和 FIN 通常分开发。CLOSE_WAIT 堆积 = 应用层没调用 close(),是排查连接泄漏的第一信号。
TIME_WAIT 为什么存在(必被追问):
- 保证最后一个 ACK 可达:ACK 丢了对方重发 FIN,若主动方已关闭将回 RST,对方异常关闭;
- 让旧连接的重复报文在网络中自然消亡(2MSL),避免污染复用同四元组的新连接。
工程侧:TIME_WAIT 是主动方的正常状态,几万个属正常;高并发短连接压垮端口时,方案是长连接/连接池(治本)、tcp_tw_reuse(仅出站)、加大端口范围,而不是盲目开 recycle。
💻 全栈开发专家
过程(主动关闭方视角):
text
主动方 被动方
│ ── FIN, seq=u ──────────→ │ 被动方 CLOSE_WAIT
│ ←─ ACK ───────────────── │ 主动方 FIN_WAIT_1 → FIN_WAIT_2
│ (被动方处理完数据)
│ ←─ FIN, seq=w ────────── │ 被动方 LAST_ACK
│ ── ACK ─────────────────→ │ 主动方 TIME_WAIT(2MSL)→ CLOSED为什么四次:TCP 全双工,两个方向独立关闭。被动方收到 FIN 时可能还有数据没发完,所以 ACK 先回,等应用层处理完再发自己的 FIN——中间的"半关闭"状态是四次的原因。
工程排障(这个最有价值):
- 大量 CLOSE_WAIT:一定是我方代码没调 close()(对方已经 FIN 了,我们在等应用关闭)——连接泄漏,查连接池归还是否覆盖异常分支。我曾定位过一次:HTTP 客户端早退没关响应体,CLOSE_WAIT 堆了 3 万个。
- 大量 TIME_WAIT:主动短连接的必然产物,2MSL(Linux 60s)。本身是正常的,量太大才需要治理:改长连接/连接池;客户端开
tcp_tw_reuse(配合时间戳);调tcp_max_tw_buckets。千万别学网上瞎配tcp_tw_recycle(NAT 环境丢连接,内核已删除该参数)。
TIME_WAIT 存在的意义:保证最后一个 ACK 可达(对方重 FIN 能再应答)+ 让旧报文自然消亡不污染新连接。