TCP 三次握手与四次挥手(图解)
TCP 是面向连接、可靠的传输协议。理解三次握手和四次挥手,是面试必考题。
一、为什么需要握手?
TCP 是面向连接的协议,通信前必须建立连接,确认双方的发送和接收能力正常。
┌────────┐ ┌────────┐
│ Client │ │ Server │
└────────┘ └────────┘
│ │
│ 三次握手建立连接 │
│ ────────────────────────────────► │
│ ◄──────────────────────────────── │
│ ────────────────────────────────► │
│ │
│ 数据传输 │
│ ════════════════════════════════ │
│ │
│ 四次挥手断开连接 │
│ ────────────────────────────────► │
│ ◄──────────────────────────────── │
│ ◄──────────────────────────────── │
│ ────────────────────────────────► │
二、三次握手(建立连接)
Client Server
│ │
│─── 1. SYN=1, seq=x ─────────────►│ CLOSED → SYN_SENT
│ │ LISTEN → SYN_RCVD
│ │
│◄── 2. SYN=1, ACK=1, seq=y, ack=x+1│
│ │
│─── 3. ACK=1, seq=x+1, ack=y+1 ──►│ ESTABLISHED
│ │
│═══ ESTABLISHED,开始传数据 ═══════│
详细步骤
第一次握手:客户端发送 SYN 报文
- SYN = 1(建立连接标志)
- seq = x(随机初始序列号)
- 客户端进入
SYN_SENT状态
第二次握手:服务器收到 SYN,回复 SYN+ACK
- SYN = 1, ACK = 1
- seq = y(服务器随机序列号)
- ack = x + 1(确认客户端的 SYN)
- 服务器进入
SYN_RCVD状态
第三次握手:客户端收到 SYN+ACK,回复 ACK
- ACK = 1
- seq = x + 1
- ack = y + 1
- 双方都进入
ESTABLISHED状态
为什么是三次?两次不行吗?
核心原因:防止已失效的连接请求到达服务器,造成资源浪费。
假设只有两次握手:
1. 客户端发出 SYN1(网络延迟,未送达)
2. 客户端超时,重发 SYN2
3. SYN2 到达,服务器回复 ACK,建立连接
4. 通信完成,断开
5. SYN1 延迟到达服务器
6. 服务器回复 ACK,建立连接(但客户端不需要,服务器一直等)
三次握手可以避免:客户端收到 ACK 后还可以判断这个连接是否真的需要。
三、四次挥手(断开连接)
Client Server
│ │
│─── 1. FIN=1, seq=u ─────────────►│ FIN_WAIT_1
│ │ CLOSE_WAIT
│◄── 2. ACK=1, seq=v, ack=u+1 ────│ FIN_WAIT_2
│ │
│ (服务器处理剩余数据) │
│ │
│◄── 3. FIN=1, ACK=1, seq=w, ack=u+1│ LAST_ACK
│ │
│─── 4. ACK=1, seq=u+1, ack=w+1 ──►│ TIME_WAIT
│ │ CLOSED
│ 等待 2MSL │
│ CLOSED │
详细步骤
第一次挥手:客户端发送 FIN
- FIN = 1, seq = u
- 客户端进入
FIN_WAIT_1,表示没有数据要发送了
第二次挥手:服务器回复 ACK
- ACK = 1, ack = u + 1
- 服务器进入
CLOSE_WAIT - 客户端进入
FIN_WAIT_2
第三次挥手:服务器发送 FIN
- FIN = 1, seq = w
- 服务器进入
LAST_ACK
第四次挥手:客户端回复 ACK
- ACK = 1, ack = w + 1
- 客户端进入
TIME_WAIT,等待 2MSL 后CLOSED - 服务器收到 ACK 后
CLOSED
为什么挥手是四次?
因为 TCP 是全双工的:
- 客户端发 FIN 表示「我没数据发了」
- 但服务器可能还有数据要发,所以 ACK 和 FIN 要分开发
- 等服务器数据发完,再发 FIN
为什么 TIME_WAIT 要等 2MSL?
MSL(Maximum Segment Lifetime):报文最大生存时间。
两个原因:
- 保证最后的 ACK 到达:如果服务器没收到 ACK,会重发 FIN,客户端还能再发一次 ACK
- 让本次连接的所有报文消失:防止延迟报文影响新连接
四、状态机总览
┌──────────────┐
│ CLOSED │
└──────┬───────┘
主动打开 │ │ 被动打开
▼ ▼
┌─────────────┐ ┌──────────────┐
│ SYN_SENT │ │ LISTEN │
└──────┬──────┘ └──────┬───────┘
收到SYN+ACK │ │ 收到SYN
发送ACK │ │ 发送SYN+ACK
▼ ▼
┌──────────────────────────────┐
│ ESTABLISHED │
└──────┬───────────────┬───────┘
主动关闭 │ │ 被动关闭
▼ ▼
┌──────────────┐ ┌──────────────┐
│ FIN_WAIT_1 │ │ CLOSE_WAIT │
└──────┬───────┘ └──────┬───────┘
│ 收到ACK │ 应用关闭
▼ ▼
┌──────────────┐ ┌──────────────┐
│ FIN_WAIT_2 │ │ LAST_ACK │
└──────┬───────┘ └──────┬───────┘
│ 收到FIN │ 收到ACK
│ 发送ACK ▼
▼ CLOSED
┌──────────────┐
│ TIME_WAIT │
└──────┬───────┘
│ 2MSL
▼
CLOSED
五、常见面试题
Q1:三次握手可以携带数据吗?
第一次和第二次不行(防止 SYN 洪泛攻击),第三次可以携带数据。
Q2:SYN 洪泛攻击是什么?怎么防御?
攻击者伪造大量 SYN 请求,服务器分配资源等待第三次握手,最终耗尽资源。
防御:
- SYN Cookie:服务器不分配资源,用加密 Cookie 验证
- 限制半连接队列大小
- 防火墙过滤
Q3:连接队列满了会怎样?
Linux 有两个队列:
- syn queue(半连接):收到 SYN,等待 ACK
- accept queue(全连接):完成握手,等待应用 accept
队列满时,新的连接请求会被丢弃或回 RST。
Q4:已经建立的连接,断电会怎样?
TCP 没有机制感知断电。如果长时间无数据,可以开启 KeepAlive(默认 2 小时)。
应用层一般会自己实现心跳。
六、抓包实战
# 用 tcpdump 抓包
sudo tcpdump -n host www.xiaoyuzhou.com and port 80
# 用 Wireshark 查看 TCP 流
# 过滤条件:tcp.stream eq 0
七、小结
| 阶段 | 步骤 | 关键标志 |
| ---- | ---- | ---- |
| 建立连接 | 3 次握手 | SYN, SYN+ACK, ACK |
| 数据传输 | 双向流 | seq, ack |
| 断开连接 | 4 次挥手 | FIN, ACK, FIN, ACK |
记住小宇宙的口诀:
- 三次握手:确认双方的发送和接收能力
- 四次挥手:全双工,要分别关闭两个方向
- TIME_WAIT 2MSL:保证 ACK 到达 + 报文消失