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):报文最大生存时间。

两个原因:

  1. 保证最后的 ACK 到达:如果服务器没收到 ACK,会重发 FIN,客户端还能再发一次 ACK
  2. 让本次连接的所有报文消失:防止延迟报文影响新连接

四、状态机总览

                              ┌──────────────┐
                              │   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 到达 + 报文消失