Skip to content

模块导航: 总入口 · 总纲与方法 · 后端场景 · AI与算法工程 · 国企与金融 · 网络问答


2. TCP 四次挥手与 TIME_WAIT(热度 654 · 核心必考) ​

Q: 描述四次挥手过程。为什么是四次?TIME_WAIT 为什么要等 2MSL?

A:

完整过程(报文 + 状态机) ​

diagram
主动关闭方(A)                     被动关闭方(B)
  |  ESTABLISHED                   |  ESTABLISHED
  |--- FIN=1, seq=u ------------->|  CLOSE-WAIT
  |  FIN-WAIT-1                    |  (应用仍可发数据)
  |<-- ACK=1, ack=u+1, seq=v -----|
  |  FIN-WAIT-2                    |
  |     (B 继续发送剩余数据)       |
  |<-- FIN=1, seq=w --------------|  LAST-ACK
  |--- ACK=1, ack=w+1 ----------->|
  |  TIME-WAIT(等2MSL)            |  CLOSED(收到ACK后)
  |  → CLOSED                      |
  1. A→B:FIN=1, seq=u:A 告诉 B「我没有数据要发了」(半关闭:仍可接收)。A 进入 FIN-WAIT-1。
  2. B→A:ACK=1, ack=u+1:B 确认。B 进入 CLOSE-WAIT。此时 B 应用层可能还有数据要发完。
  3. B→A:FIN=1, seq=w:B 数据发完,也发 FIN。B 进入 LAST-ACK。
  4. A→B:ACK=1, ack=w+1:A 确认,进入 TIME-WAIT,等待 2MSL 后进入 CLOSED。B 收到 ACK 后 CLOSED。

补充状态: 若 A 在 FIN-WAIT-1 收到 FIN(B 也立刻关),可同时确认,进入 CLOSING,再收 ACK 后 TIME-WAIT;或 B 的 ACK 与 FIN 一起到,A 直接从 FIN-WAIT-1 进 TIME-WAIT。

设计动机:为什么是四次? ​

  • TCP 全双工,每个方向要单独关闭。 FIN 只表示「这个方向我不再发送」。
  • 被动方收到 FIN 后可能还有数据要发,所以 ACK(确认你的 FIN)和 FIN(我也关了)不保证能合并——这正是不像握手能省一次的原因。
  • 半关闭(half-close)有实际价值: 如客户端发完请求后 shutdown write,服务器仍能把剩余响应发完再关。

TIME_WAIT 等 2MSL 的原因 ​

MSL(Maximum Segment Lifetime)是报文最大生存时间。

  1. 保证最后一个 ACK 能到达: 若最后的 ACK 丢失,被动方会重传 FIN;主动方仍在 TIME-WAIT 才能再发 ACK。若主动方立刻消失,被动方会卡在 LAST-ACK。
  2. 让本次连接的旧报文在网络中自然消亡: 等待超过「报文最长寿命」后,相同四元组(源IP、源端口、目的IP、目的端口)的新连接不会误收旧报文。

Linux 实际: Linux 的 TIME-WAIT 时长是内核常量 TCP_TIMEWAIT_LEN = 60 秒,写死在 include/net/tcp.h 里,不可通过 sysctl 调整,也与 tcp_fin_timeout 无关(tcp_fin_timeout 管的是没有文件描述符引用的孤儿连接在 FIN-WAIT-2 的停留时间,默认 60s——两者常被混为一谈)。RFC 793 定义的 MSL = 2 分钟,按 2MSL 算应是 4 分钟;教材常写 2MSL(谢希仁第8版取 MSL=2 min ⇒ 4 min),Linux 用 60s 是工程折中(内核 tcp.h 里它就是个写死的常数,注释并未按 2×MSL 推导;RFC 793 的 MSL=2 min 若要 2MSL 应是 4 min。别临时编一个「报文最大存活 30s」去调和这两个数——那没有出处)。答题口径:规范是 2MSL,Linux 实现固定 60s。

加分点 ​

  • 高并发短连接服务器大量 TIME_WAIT:优化 tcp_tw_reuse(安全前提下复用)、tcp_max_tw_buckets、连接池/长连接。TIME_WAIT 是正常现象;CLOSE_WAIT 堆积才是 bug(见第 18 题)。
  • 主动关闭方才会 TIME-WAIT:「谁 TIME_WAIT 谁主动关」——排查时看 TIME_WAIT 出现在服务端还是客户端。
  • tcp_tw_recycle 因在 NAT 环境下时间戳不一致会误杀新连接,Linux 4.12 起已彻底删除(不是"不推荐",而是新版本根本没有这个开关);tcp_tw_reuse 仍在,取值 0/1/2(0=关闭;1=全局启用;2=只对回环流量启用,内核 ip-sysctl.rst 原文为 "1 - global enable / 2 - enable for loopback traffic only")。默认值可核:Linux 4.17 及以前是 0/1 两值、默认 0;4.18 起默认 2(本机 7.0.0 内核 sysctl net.ipv4.tcp_tw_reuse 实测为 2)。可见默认值只允许复用回环连接的 TIME_WAIT 套接字,要让出站公网连接也复用需显式设成 1;另外复用还要求对端启用了时间戳、且该套接字进入 TIME_WAIT 至今超过 tcp_tw_reuse_delay(默认 1000 ms;内核比的是 tw->tw_entry_stamp,不是「上次收到对端时间戳」的 tw_ts_recent_stamp)。

常见追问 ​

追问 1:为什么握手 3 次、挥手 4 次?

  • 答题要点: 握手时服务器 SYN+ACK 可合并;挥手时被动方的 ACK 与 FIN 不保证能合并——收到 FIN 只代表「对方不发了」,本方可能还有数据要发,分开走才能保住全双工与半关闭;但这是「不保证」不是「禁止」:本方无待发数据时两者可以同段到达,线上只剩 3 个报文(见上面状态机那条)。

追问 2:大量 TIME_WAIT 怎么处理?一定是问题吗?

  • 答题要点: 不一定是问题,是主动关闭方短连接多的正常结果。问题在于耗尽本地端口/Ephemeral port 或文件描述符相关资源。处理:① 应用层连接池/HTTP keep-alive;② net.ipv4.tcp_tw_reuse=1(允许对出站连接复用 TIME_WAIT);③ 调大端口范围 ip_local_port_range;④ 别再去调 tcp_tw_recycle——4.12 起已删除(见上),新内核根本没有这个开关。先用 ss -tan state time-wait 确认分布。

追问 3:如果服务器出现大量 CLOSE_WAIT,和 TIME_WAIT 有什么本质区别?

  • 答题要点: TIME_WAIT 多 = 主动关闭方短连接多(可优化,不一定有 bug)。CLOSE_WAIT 多 = 本端收到了对端 FIN,但应用层迟迟没调用 close()——典型代码 bug(连接泄漏、异常未捕获、线程阻塞、连接池未归还)。见第 18 题。

易错点 ​

易错说法纠正
「TIME_WAIT 出现在被动关闭方」出现在主动关闭方;被动方是 CLOSE_WAIT → LAST_ACK
「2MSL 是为了让数据传完」数据在 FIN/ACK 阶段已谈完发送方向;2MSL 为 ACK 可达性 + 旧报文消亡
「TIME_WAIT 很多说明网络故障」多数情况是短连接负载高,属正常;故障信号更多是 CLOSE_WAIT/SYN_RECV 异常
「四次挥手是因为 TCP 不可靠」四次源于全双工 + 半关闭语义,与可靠重传机制不是同一层原因
「服务器 TIME_WAIT 一定该关掉」不能用内核 hack 一味清 TIME_WAIT;优先改连接模型

与选择题对照 ​

  • 主库 150: 第 60 题(握手/挥手次数)、第 61 题(第二步 SYN/ACK 的值)、第 62 题(主动关闭方最后一个报文=ACK)、第 63 题(TIME_WAIT 时长)、第 138 题(大量 CLOSE_WAIT 含义)。
  • 补题 16: 补-11(TCP 标志位,FIN/ACK 等);与补-12 形成「建连/释放」对照。
  • 口述联动: 选择题侧重「次数/标志位/时长」,面试侧重「为什么 4 次、2MSL 两个原因、TIME_WAIT vs CLOSE_WAIT」。

工程 / 抓包背景 ​

  • ss 命令:
    • ss -tan state time-wait | wc -l
    • ss -tan state close-wait
    • ss -tan sport = :80 or dport = :80
  • Wireshark: 过滤 tcp.flags.fin==1;观察 FIN/ACK 时序;tcp.analysis.flags 可看重传。
  • sysctl: net.ipv4.tcp_fin_timeout、net.ipv4.tcp_tw_reuse、net.ipv4.tcp_max_tw_buckets、net.ipv4.ip_local_port_range。
  • Nginx: keepalive_timeout、keepalive_requests;上游 proxy_http_version 1.1; proxy_set_header Connection ""; 以复用上游连接,减少服务端主动关闭。

持续学习,持续积累。