TCP 三次握手与四次挥手详解
TCP 是面向连接的可靠传输协议,三次握手建立连接、四次挥手断开连接是网络面试的高频考点。
一、TCP 协议基础
1.1 TCP 的特点
| 特点 | 说明 |
|---|---|
| 面向连接 | 通信前需要建立连接 |
| 可靠传输 | 保证数据不丢失、不重复、按序到达 |
| 全双工 | 双方可以同时发送数据 |
| 面向字节流 | 以字节为单位传输 |
| 流量控制 | 滑动窗口机制 |
| 拥塞控制 | 慢启动、拥塞避免等 |
1.2 TCP 报文段头部
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |C|E|U|A|P|R|S|F| |
| Offset| Rsrvd |W|C|R|C|S|S|Y|I| Window |
| (4) | (3) |R|E|G|K|H|T|N|N| (16) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+关键字段:
| 字段 | 说明 |
|---|---|
| Sequence Number | 序列号,标识发送的数据字节流位置 |
| Acknowledgment Number | 确认号,期望收到的下一个字节 |
| SYN | 同步序列号,用于建立连接 |
| ACK | 确认标志 |
| FIN | 结束标志,用于断开连接 |
| RST | 重置连接 |
| Window | 窗口大小,用于流量控制 |
二、三次握手过程详解
2.1 连接建立过程
客户端 服务器
| |
| ---- SYN (seq=x) ----------------→ | 第一次握手
| |
| ←--- SYN+ACK (seq=y, ack=x+1) --- | 第二次握手
| |
| ---- ACK (ack=y+1) --------------→ | 第三次握手
| |
| 连接建立,可以传输数据 |2.2 详细过程
第一次握手:客户端 → 服务器
客户端发送 SYN 报文:
- SYN = 1
- seq = x(客户端初始序列号)
- 进入 SYN_SENT 状态第二次握手:服务器 → 客户端
服务器收到 SYN,发送 SYN+ACK:
- SYN = 1, ACK = 1
- seq = y(服务器初始序列号)
- ack = x + 1(确认客户端序列号)
- 进入 SYN_RCVD 状态第三次握手:客户端 → 服务器
客户端收到 SYN+ACK,发送 ACK:
- ACK = 1
- seq = x + 1
- ack = y + 1(确认服务器序列号)
- 进入 ESTABLISHED 状态
- 服务器收到后也进入 ESTABLISHED 状态2.3 状态转换图
客户端状态变化: 服务器状态变化:
CLOSED
↓ 收到 listen
LISTEN
CLOSED
↓ connect()
SYN_SENT ──── SYN ────→ SYN_RCVD
↓ 收到 SYN+ACK ↓ 收到 ACK
ESTABLISHED ←── ACK ──── ESTABLISHED三、为什么是三次而不是两次
3.1 历史连接问题
假设只有两次握手:
场景:网络中存在延迟的旧 SYN 报文
客户端 服务器
| |
| ---- SYN (seq=100, 旧报文) -------→ | ← 服务器以为是新连接
| |
| ←--- SYN+ACK (seq=200) ----------- | ← 服务器分配资源
| |
| 客户端不知道这个连接,不会响应 | ← 服务器资源浪费!
服务器误建立了连接,但客户端根本不知道三次握手的解决:
客户端 服务器
| |
| ---- SYN (seq=100, 旧报文) -------→ |
| |
| ←--- SYN+ACK (seq=200) ----------- |
| |
| 客户端发现这不是自己发起的连接, |
| 发送 RST 拒绝连接 |
| ---- RST ───────────────────────→ |
| |
| 服务器释放资源,不会建立无效连接 |3.2 同步序列号
两次握手无法确认双方的接收能力:
场景:服务器的 SYN+ACK 丢失
两次握手:
客户端 ──SYN──→ 服务器 ← SYN+ACK 丢失
↓
服务器以为连接建立了
客户端不知道连接建立了
双方状态不一致!
三次握手:
客户端 ──SYN──→ 服务器 ← SYN+ACK 丢失
↓ 超时重传 SYN
客户端 ──SYN──→ 服务器
↓ 收到 SYN+ACK
客户端 ──ACK──→ 服务器 ← 双方都确认连接建立核心理解
三次握手的本质:确认双方的发送能力和接收能力都正常。
- 第一次:服务器确认客户端能发、自己能收
- 第二次:客户端确认自己能发能收、服务器能发能收
- 第三次:服务器确认自己能发能收、客户端能发能收
四、四次挥手过程详解
4.1 连接断开过程
客户端 服务器
| |
| ---- FIN (seq=u) ----------------→ | 第一次挥手
| |
| ←--- ACK (ack=u+1) -------------- | 第二次挥手
| |
| (服务器可能还有数据要发送) |
| |
| ←--- FIN (seq=w) ---------------- | 第三次挥手
| |
| ---- ACK (ack=w+1) --------------→ | 第四次挥手
| |
| 连接关闭 |4.2 详细过程
第一次挥手:客户端 → 服务器
客户端发送 FIN:
- FIN = 1
- seq = u
- 表示"我没有数据要发了"
- 进入 FIN_WAIT_1 状态第二次挥手:服务器 → 客户端
服务器收到 FIN,发送 ACK:
- ACK = 1
- ack = u + 1
- 表示"我知道你没数据了"
- 进入 CLOSE_WAIT 状态
- 客户端收到后进入 FIN_WAIT_2 状态第三次挥手:服务器 → 客户端
服务器发送完剩余数据后,发送 FIN:
- FIN = 1
- seq = w
- 表示"我也没数据要发了"
- 进入 LAST_ACK 状态第四次挥手:客户端 → 服务器
客户端收到 FIN,发送 ACK:
- ACK = 1
- ack = w + 1
- 进入 TIME_WAIT 状态
- 等待 2MSL 后关闭
- 服务器收到后立即关闭4.3 状态转换图
客户端状态变化: 服务器状态变化:
ESTABLISHED ESTABLISHED
↓ 主动关闭 ↓ 收到 FIN
FIN_WAIT_1 CLOSE_WAIT
↓ 收到 ACK ↓ 被动关闭
FIN_WAIT_2 LAST_ACK
↓ 收到 FIN ↓ 收到 ACK
TIME_WAIT CLOSED
↓ 2MSL 超时
CLOSED五、为什么是四次而不是三次
5.1 全双工的特点
TCP 是全双工协议,数据可以在两个方向上同时传输:
客户端 ←──────────────────→ 服务器
←─ 数据流 A ─→
←─ 数据流 B ─→
关闭连接需要分别关闭两个方向的数据流5.2 被动方可能有数据要发送
场景:服务器收到 FIN 时还有数据没发完
客户端 服务器
| FIN → | ← 服务器收到 FIN
| ← ACK | ← 先确认,表示知道了
| | ← 继续发送剩余数据
| ← DATA |
| ← DATA |
| ← FIN | ← 数据发完后,再发 FIN
| ACK → | ← 客户端确认5.3 为什么不能合并第二次和第三次挥手
如果服务器收到 FIN 时没有数据要发:
理论上可以合并:
客户端 服务器
| FIN → |
| ← FIN+ACK | ← 合并挥手
但大多数实现不合并:
- 服务器可能还有数据要发送
- 实现更简单、更通用
- 被动方的 ACK 和 FIN 之间可能有时间间隔理解关键
四次挥手的原因:TCP 是全双工的,关闭连接需要两个方向分别关闭。收到 FIN 只表示对方没有数据发了,自己可能还有数据要发。
六、TIME_WAIT 状态的作用
6.1 TIME_WAIT 持续时间
TIME_WAIT 持续 2MSL(Maximum Segment Lifetime)
MSL:报文最大生存时间
- Linux: MSL = 60秒
- Windows: MSL = 120秒
- 2MSL = 2 × MSL = 120-240秒6.2 为什么需要 TIME_WAIT
原因1:确保最后的 ACK 能到达
客户端 服务器
| ACK → | ← 如果这个 ACK 丢失
| |
| (TIME_WAIT)| ← 服务器会重传 FIN
| ← FIN |
| ACK → | ← 客户端重发 ACK
| |
| (2MSL 后) | ← 确保服务器收到了 ACK
| CLOSE |原因2:确保旧连接的数据完全消失
场景:如果没有 TIME_WAIT,立即建立新连接
旧连接: seq=100 的数据包在网络中延迟
新连接: 使用相同的四元组
旧连接的数据包可能被新连接误收!
TIME_WAIT = 2MSL 确保:
- 旧连接的所有数据包都已消失
- 新连接不会收到旧数据6.3 TIME_WAIT 过多的问题
bash
# 查看 TIME_WAIT 数量
netstat -an | grep TIME_WAIT | wc -l
# 问题:
# 1. 占用端口资源
# 2. 占用内存(少量)6.4 解决方案
bash
# 1. 开启 TIME_WAIT 快速回收(Linux)
net.ipv4.tcp_tw_reuse = 1 # 允许复用 TIME_WAIT 连接
# 2. 调整 MSL(不推荐)
# 3. 使用长连接代替短连接
# 4. 使用连接池tcp_tw_recycle
Linux 4.12 已移除 tcp_tw_recycle,因为它在 NAT 环境下会导致问题。
七、常见面试问题与解答
7.1 为什么握手是三次,挥手是四次?
握手:三次足够确认双方的发送和接收能力
- 服务器的 SYN 和 ACK 可以合并发送
挥手:需要四次
- 服务器收到 FIN 时可能还有数据要发
- ACK 和 FIN 通常分开发送
- TCP 全双工,两个方向需要分别关闭7.2 如果第三次握手丢失会怎样?
客户端 服务器
| |
| ---- SYN ------------------------→ |
| ←--- SYN+ACK --------------------- |
| ---- ACK (丢失) ─────────────────→ | ← ACK 丢失
| |
| 服务器: 没收到 ACK,超时重传 SYN+ACK |
| |
| ←--- SYN+ACK (重传) -------------- |
| ---- ACK --------------------------→ |
| |
| 双方进入 ESTABLISHED 状态7.3 SYN Flood 攻击是什么?
攻击者 服务器
| |
| ---- SYN (伪造源IP) --------------→ | ← 服务器分配资源
| | 等待 ACK
| ---- SYN (伪造源IP) --------------→ | ← 再次分配资源
| |
| ... 大量 SYN ... | ← 资源耗尽
| |
| 无法响应正常用户的连接请求防御方法:
1. SYN Cookie:不分配资源,用 Cookie 验证
2. 增大半连接队列
3. 缩短 SYN_RCVD 超时时间
4. 防火墙限制 SYN 速率7.4 TCP 和 UDP 的区别?
| 对比项 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 传输方式 | 字节流 | 数据报 |
| 速度 | 较慢 | 较快 |
| 头部开销 | 20 字节 | 8 字节 |
| 流量控制 | 有 | 无 |
| 拥塞控制 | 有 | 无 |
| 应用场景 | Web、文件传输 | 视频、游戏、DNS |
7.5 如果客户端突然断电会怎样?
场景:客户端断电,没有发送 FIN
服务器:
- 一直保持 ESTABLISHED 状态
- 开启 Keep-Alive 探测:
- 定期发送探测报文
- 多次无响应后关闭连接
配置:
- tcp_keepalive_time = 7200 (2小时)
- tcp_keepalive_intvl = 75 (75秒)
- tcp_keepalive_probes = 9 (9次)八、Wireshark 抓包分析
8.1 抓包准备
bash
# 安装 Wireshark
# 启动抓包,选择网卡
# 过滤 TCP 流
tcp.stream eq 0
# 过滤特定主机
ip.addr == 192.168.1.100
# 过滤 SYN 包
tcp.flags.syn == 1
# 过滤三次握手
tcp.flags.syn == 1 || tcp.flags.fin == 18.2 正常三次握手
No. Time Source Dest Protocol Info
1 0.000 192.168.1.100 93.184.216.34 TCP SYN
2 0.023 93.184.216.34 192.168.1.100 TCP SYN, ACK
3 0.023 192.168.1.100 93.184.216.34 TCP ACK
分析:
- 包1: seq=0, 客户端初始序列号
- 包2: seq=0, ack=1, 服务器初始序列号,确认客户端
- 包3: seq=1, ack=1, 确认服务器8.3 正常四次挥手
No. Time Source Dest Protocol Info
10 5.000 192.168.1.100 93.184.216.34 TCP FIN, ACK
11 5.023 93.184.216.34 192.168.1.100 TCP ACK
12 5.100 93.184.216.34 192.168.1.100 TCP FIN, ACK
13 5.100 192.168.1.100 93.184.216.34 TCP ACK九、总结
9.1 三次握手
客户端 ──SYN──→ 服务器 第一次:客户端请求建立连接
客户端 ←─SYN+ACK─ 服务器 第二次:服务器确认并同步
客户端 ──ACK──→ 服务器 第三次:客户端确认9.2 四次挥手
客户端 ──FIN──→ 服务器 第一次:客户端请求关闭
客户端 ←─ACK─── 服务器 第二次:服务器确认
客户端 ←─FIN─── 服务器 第三次:服务器请求关闭
客户端 ──ACK──→ 服务器 第四次:客户端确认9.3 核心要点
| 问题 | 答案 |
|---|---|
| 为什么三次握手 | 防止历史连接,同步双方序列号 |
| 为什么四次挥手 | 全双工,两个方向分别关闭 |
| TIME_WAIT 的作用 | 确保 ACK 到达,防止旧数据干扰 |
| TIME_WAIT 持续多久 | 2MSL (120-240秒) |
面试准备
- 能够画出三次握手和四次挥手的状态转换图
- 理解每个状态的含义和转换条件
- 能够解释为什么是三次/四次
- 了解 SYN Flood 攻击和防御
- 知道 TIME_WAIT 的作用和相关配置
