TCP 连接管理
三次握手、四次挥手、TCP 状态机与 TIME_WAIT — 这是面试最高频的 TCP 考点。
1. 为什么需要三次握手?
TCP 是面向连接的协议。通信前,双方需要确认彼此的收发能力正常,同时交换初始序列号(ISN)。
核心问题:网络中存在延迟的旧 SYN 段,如果没有三次握手,可能错误地建立"幽灵连接"。三次握手确保双方都确认了对方的序列号,且连接请求是"新鲜的"。
RFC 9293 明确指出:三次握手是必要的,因为序列号不绑定到网络的全局时钟,接收方无法知道收到的 SYN 是不是旧的,必须让发送方验证。
2. 三次握手(Three-Way Handshake, 3WHS)
以客户端(主动打开)连接服务器(被动打开)为例:
客户端 服务器
| |
CLOSED LISTEN
| |
① |── SYN, seq=x ───────────────→ | SYN-RECEIVED
| |
② |←─ SYN+ACK, seq=y, ack=x+1 ───| SYN-RECEIVED
| |
ESTABLISHED |
③ |── ACK, seq=x+1, ack=y+1 ────→ | ESTABLISHED
| |
各步详解
| 步骤 | 方向 | 内容 | 客户端状态 | 服务器状态 |
|---|---|---|---|---|
| ① | C→S | SYN=1, seq=x(客户端 ISN) | SYN-SENT | SYN-RECEIVED |
| ② | S→C | SYN=1, ACK=1, seq=y, ack=x+1 | ESTABLISHED | SYN-RECEIVED |
| ③ | C→S | ACK=1, seq=x+1, ack=y+1 | ESTABLISHED | ESTABLISHED |
关键点:
- 步骤 ② 中将 SYN 和 ACK 合并为一条消息("捎带"),这是能省掉一次往返的关键
- 步骤 ③ 的 ACK 不消耗序列号(纯 ACK 段 seq 不变)
- 连接建立后,后续数据段 seq 从各自 ISN+1 开始
为什么不是两次握手?
如果只有两次握手:客户端发 SYN → 服务器回 SYN+ACK 就认为连接建立。但若客户端第一个 SYN 在网络中延迟,客户端超时后重新发了 SYN 并完成通信。之后旧 SYN 到达服务器,服务器回 SYN+ACK 就建立了无效连接,浪费资源。
3. TCP 状态机
RFC 9293 定义了 11 个 TCP 状态:
+---------+
| CLOSED |
+---------+
| ^
passive OPEN | | CLOSE
V |
+---------+
| LISTEN |
+---------+
| |
rcv SYN | snd SYN
| V
+---------+ +---------+
| SYN |<-- rcv SYN ------| SYN |-- snd SYN,ACK --> +---------+
| RCVD | | SENT | | SYN |
+---------+-- snd SYN,ACK -->+---------+<-- rcv SYN,ACK --| RCVD |
| | +---------+
| rcv ACK of SYN | rcv ACK of SYN |
V V |
+------------------- ESTABLISHED -----------------------------+
| |
CLOSE| rcv FIN
V V
+---------+ +---------+
| FIN | | CLOSE |
| WAIT-1 | | WAIT |
+---------+ +---------+
| |
V V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | |LAST-ACK |
+---------+ +---------+ +---------+
| | |
V V V
+---------+ +---------+ +---------+
|TIME-WAIT| | CLOSED | | CLOSED |
+---------+ +---------+ +---------+
状态分类记忆:
| 阶段 | 状态 | 说明 |
|---|---|---|
| 初始 | CLOSED, LISTEN | 起点 |
| 握手 | SYN-SENT, SYN-RECEIVED | 三次握手过程 |
| 传输 | ESTABLISHED | 数据传输 |
| 挥手 | FIN-WAIT-1/2, CLOSING, CLOSE-WAIT, LAST-ACK | 关闭过程 |
| 结束 | TIME-WAIT, CLOSED | 收尾 |
4. 四次挥手(Four-Way Handshake)
TCP 是全双工的,每个方向独立关闭。关闭自己这一端的发送方向需要 FIN:
主动关闭方 (A) 被动关闭方 (B)
ESTABLISHED ESTABLISHED
| |
① FIN-WAIT-1 |
|── FIN, seq=u ───────────→ | CLOSE-WAIT
| |
② FIN-WAIT-2 |
|←── ACK, seq=v, ack=u+1 ───| CLOSE-WAIT
| |
| (应用处理中)
| |
③ TIME-WAIT | LAST-ACK
|←── FIN, seq=w, ack=u+1 ───|
| |
④ TIME-WAIT |
|── ACK, seq=u+1, ack=w+1 ─→ | CLOSED
| |
2MSL 后 CLOSED
为什么是四次而不是三次?
因为 TCP 是全双工的。收到 FIN 只表示对方不再发送数据,本方还可以继续发送。本方也发 FIN 才表示本方的发送方向也关闭。中间的 ACK 和 FIN 通常不能合并——ACK 是立即回复的,而 FIN 需要等应用层调用 close()。
5. TIME_WAIT 状态详解
主动关闭方在发送最后一个 ACK 后进入 TIME_WAIT,持续 2MSL(Maximum Segment Lifetime,通常 2 分钟,实际实现中常为 30 秒到 2 分钟)。
TIME_WAIT 存在的两个原因(RFC 9293)
- 确保最后的 ACK 能被对方收到:如果最后的 ACK 丢失,被动方会重传 FIN,主动方还在 TIME_WAIT 就能重发 ACK。
- 让旧连接的所有段从网络中消失:防止旧连接的延迟段被新连接(相同四元组)误接收。
常见面试问题
Q: 大量 TIME_WAIT 怎么处理?
- 调整内核参数:
tcp_tw_reuse(复用 TIME_WAIT 连接)、tcp_tw_recycle(快速回收,已废弃) - 使用长连接代替短连接
- 让客户端主动关闭(TIME_WAIT 在客户端,影响较小)
Q: 为什么 TIME_WAIT 是 2MSL?
- 1 个 MSL 给最后的 ACK 到达对端,1 个 MSL 给可能重传的 FIN 回来。总共 2MSL 足够保证安全。
6. 同时打开与同时关闭
同时打开:双方同时发送 SYN,都进入 SYN-SENT,收到对方 SYN 后进入 SYN-RECEIVED 并回复 SYN+ACK。结果只建立一条连接。
同时关闭:双方同时发送 FIN,都进入 FIN-WAIT-1 → 收到 FIN 后进入 CLOSING → 收到 ACK 后进入 TIME-WAIT。
本篇要点
- 三次握手:客户端发 SYN → 服务器回 SYN+ACK → 客户端回 ACK,双方交换初始序列号并确认收发能力
- 三次握手不能省为两次:防止网络中延迟的旧 SYN 导致服务器错误建立连接
- 四次挥手:每个方向独立关闭(FIN 和 ACK),全双工特性决定了不能合并
- TIME_WAIT 持续 2MSL,作用:① 确保最后的 ACK 到达对方;② 让旧连接的延迟段从网络中消失
- 客户端状态:CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
- 服务端状态:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
- 大量 CLOSE_WAIT 说明应用层没有正确关闭 socket(代码 bug);大量 TIME_WAIT 说明短连接过多
- SYN Flood 攻击用伪造 IP 的 SYN 耗尽半连接队列,防御手段:SYN Cookie、防火墙限速、增大队列