公告

👇微信扫码添加好友👇

图片
Skip to content

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 的区别?

对比项TCPUDP
连接面向连接无连接
可靠性可靠不可靠
传输方式字节流数据报
速度较慢较快
头部开销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 == 1

8.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秒)

面试准备

  1. 能够画出三次握手和四次挥手的状态转换图
  2. 理解每个状态的含义和转换条件
  3. 能够解释为什么是三次/四次
  4. 了解 SYN Flood 攻击和防御
  5. 知道 TIME_WAIT 的作用和相关配置