连载中 5/20

四次挥手与 TIME_WAIT:分手为什么这么麻烦

2026-10-03 · 28 阅读 · 0 评论 · 0 赞

四次挥手全过程

主动方(客户端)                被动方(服务端)
   │ ① FIN, seq=u                 │
   │ ────────────────────────────→ │  被动方 CLOSE_WAIT:你 说完了,但我还有话
   │ ② ACK, ack=u+1               │
   │ ←──────────────────────────── │
   │                               │  (被动方继续发剩下的数据……)
   │ ③ FIN, ACK, seq=w, ack=u+1   │
   │ ←──────────────────────────── │  被动方发完了,也 关
   │ ④ ACK, ack=w+1               │
   │ ────────────────────────────→ │
   │ TIME_WAIT,等 2MSL            │  被动方 CLOSED
   │ CLOSED                        │

为什么挥手比握手多一次?因为 TCP 是全双工且允许半关闭:客户端发 FIN 只表示我没有数据要发了,但仍可以收;服务端可能还有数据在路上,要等它也发完、也发 FIN,两个方向才算都关了。握手时 SYN 和 ACK 能合并,挥手时中间那次 ACK 和 FIN 之间隔着服务端收尾的时间,合不了。

TIME_WAIT:主动方的 2MSL 修行

主动关闭方发出最后一个 ACK 后,不立刻关,要等 2MSL(报文最大生存时间的两倍,Linux 上约 60 秒)。等什么?两件事:

// 1. 兜底对方的 FIN 重传
//    若最后的 ACK 丢了,被动方会重发 FIN
//    TIME_WAIT 状态还能回 ACK——一关就回 RST,对方懵
//
// 2. 让本连接的旧报文自然死亡
//    等 2MSL,这条连接的所有迟到包都活不过这个窗口
//    否则同样的四元组复用给新连接,旧数据串门就完了

所以 TIME_WAIT 不是 bug,是可靠性换来的税。谁主动关连接,谁交这笔税。

几千个 TIME_WAIT 要不要治

先说结论:TIME_WAIT 多本身不可怕(它几乎不耗内存,占用的是端口和表项),真正要治的是它背后的短连接习惯。常见两招:

# 统计各状态连接数——排查第一步
$ ss -s
TCP:   8421 (estab 120, closed 0, orphaned 0, timewait 3900)

# 治标:开启 TIME_WAIT 复用(出方向连接可复用旧端口)
$ sysctl -w net.ipv4.tcp_tw_reuse=1     # 只对客户端(主动连接方)生效

# 治本:改成长连接——HTTP keepalive、数据库连接池
# 让连接建一次用一万次,TIME_WAIT 从根上消失

警告:网上教程常让你开 tcp_tw_recycle,千万别开——它按源 IP 记时间戳,NAT 环境下多个用户共享一个出口 IP,时间戳乱序直接丢包,堪称经典事故配方(新内核已移除该参数)。

CLOSE_WAIT 才是真警报

分清两个状态的责任人:TIME_WAIT 堆在自己机器上,说明你是主动关闭方——通常是连接管理策略问题,可用连接池化解;CLOSE_WAIT 堆积则是自己代码的锅——对方已发 FIN 关闭,你的应用收到后一直不调 close(),八成是异常分支忘了关连接、线程池阻塞在业务逻辑上没走到 finally。线上看到几千 CLOSE_WAIT,直接查应用代码,不用怀疑网络。下一篇进入传输的精髓:滑动窗口——TCP 凭什么做到不丢、不重、不乱序。

☕
503

10 年全栈工程师 · 503咖啡馆主理人

#四次挥手#TIME_WAIT#CLOSE_WAIT#半关闭#连接复用

评论 (0)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10100 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9872 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9293 阅读 · 21 评论 · 287 赞