概述
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在 TCP 通信中,连接的建立需要通过三次握手(Three-Way Handshake)完成,连接的释放则需要通过四次挥手(Four-Way Handshake)完成。这两个过程是 TCP 可靠传输的基础保障。
一、TCP 报文段中的关键标志位
在理解三次握手和四次挥手之前,需要先了解 TCP 报文段头部中的几个重要标志位(Flag):
| 标志位 | 全称 | 说明 |
|---|---|---|
| SYN | Synchronize | 同步序列编号,用于建立连接时发起请求 |
| ACK | Acknowledge | 确认应答,表示对收到数据的确认 |
| FIN | Finish | 结束标志,用于释放连接时发起关闭请求 |
| RST | Reset | 重置连接,用于异常情况下强制断开连接 |
| SEQ | Sequence Number | 序列号,标识发送数据的字节序号 |
| ACK Number | Acknowledgment Number | 确认号,期望收到对方下一个字节的序号 |
二、三次握手(连接建立)
2.1 流程时序图
sequenceDiagram
participant C as 客户端 (Client)
participant S as 服务器 (Server)
Note over C: 状态: CLOSED
Note over S: 状态: LISTEN
C->>S: 1. SYN, seq=x
Note over C: 状态: SYN_SENT
S->>C: 2. SYN+ACK, seq=y, ack=x+1
Note over S: 状态: SYN_RCVD
C->>S: 3. ACK, seq=x+1, ack=y+1
Note over C: 状态: ESTABLISHED
Note over S: 状态: ESTABLISHED
2.2 各步骤详解
第一次握手:客户端发送 SYN 报文
- 客户端向服务器发送一个 SYN 报文段。
- 报文中设置 SYN=1,并携带一个随机生成的初始序列号 seq=x。
- 客户端进入
SYN_SENT状态,等待服务器确认。
第二次握手:服务器回复 SYN+ACK 报文
- 服务器收到 SYN 报文后,向客户端回复一个 SYN+ACK 报文段。
- 报文中设置 SYN=1、ACK=1,携带服务器自己的初始序列号 seq=y,以及确认号 ack=x+1。
- 服务器进入
SYN_RCVD状态。
第三次握手:客户端发送 ACK 报文
- 客户端收到 SYN+ACK 报文后,向服务器发送一个 ACK 报文段。
- 报文中设置 ACK=1,携带序列号 seq=x+1,确认号 ack=y+1。
- 客户端进入
ESTABLISHED状态。 - 服务器收到该 ACK 报文后,也进入
ESTABLISHED状态。 - 至此,连接正式建立,双方可以开始传输数据。
2.3 为什么需要三次握手
三次握手的核心目的是确认双方的发送能力和接收能力均正常,并同步初始序列号。
- 第一次握手:服务器确认客户端的发送能力正常。
- 第二次握手:客户端确认服务器的发送能力和接收能力正常。
- 第三次握手:服务器确认客户端的接收能力正常。
如果只有两次握手,服务器无法确认客户端是否成功收到了自己的 SYN+ACK 响应,可能导致服务器为已失效的连接请求分配资源,造成资源浪费。
2.4 为什么不是四次或更多次
三次是确认双方收发能力正常所需的最少次数。更多的握手次数不会带来额外的可靠性提升,反而会增加连接建立的延迟和开销。
三、四次挥手(连接释放)
3.1 流程时序图
sequenceDiagram
participant C as 客户端 (Client)
participant S as 服务器 (Server)
Note over C: 状态: ESTABLISHED
Note over S: 状态: ESTABLISHED
C->>S: 1. FIN, seq=u
Note over C: 状态: FIN_WAIT_1
S->>C: 2. ACK, ack=u+1
Note over C: 状态: FIN_WAIT_2
Note over S: 状态: CLOSE_WAIT
Note right of S: 服务器可能仍有数据需要发送 (半关闭状态)
S->>C: 3. FIN+ACK, seq=w, ack=u+1
Note over S: 状态: LAST_ACK
C->>S: 4. ACK, seq=u+1, ack=w+1
Note over C: 状态: TIME_WAIT
Note over S: 状态: CLOSED
Note over C: 等待 2MSL 后进入 CLOSED 状态
3.2 各步骤详解
第一次挥手:客户端发送 FIN 报文
- 客户端数据发送完毕后,向服务器发送一个 FIN 报文段。
- 报文中设置 FIN=1,携带序列号 seq=u。
- 客户端进入
FIN_WAIT_1状态,表示不再向服务器发送数据。
第二次挥手:服务器回复 ACK 报文
- 服务器收到 FIN 报文后,回复一个 ACK 报文段。
- 报文中设置 ACK=1,确认号 ack=u+1。
- 服务器进入
CLOSE_WAIT状态。 - 客户端收到 ACK 后进入
FIN_WAIT_2状态。 - 此时,客户端到服务器方向的连接已关闭,但服务器到客户端方向仍可传输数据(半关闭状态)。
第三次挥手:服务器发送 FIN 报文
- 服务器数据发送完毕后,向客户端发送一个 FIN 报文段。
- 报文中设置 FIN=1、ACK=1,携带序列号 seq=w,确认号 ack=u+1。
- 服务器进入
LAST_ACK状态,等待客户端最后确认。
第四次挥手:客户端回复 ACK 报文
- 客户端收到 FIN 报文后,回复一个 ACK 报文段。
- 报文中设置 ACK=1,携带序列号 seq=u+1,确认号 ack=w+1。
- 客户端进入
TIME_WAIT状态。 - 服务器收到 ACK 后立即进入
CLOSED状态。 - 客户端在
TIME_WAIT状态等待 2MSL(Maximum Segment Lifetime,最大报文段生存时间)后,进入CLOSED状态。
3.3 为什么需要四次挥手
TCP 是全双工通信,关闭连接时双方需要独立关闭各自的发送通道:
- 第一次和第二次挥手:关闭客户端到服务器方向的数据传输。
- 第三次和第四次挥手:关闭服务器到客户端方向的数据传输。
由于服务器收到 FIN 时可能仍有数据未发送完毕,因此服务器不能像三次握手中那样将 SYN 和 ACK 合并发送,必须分两步(先 ACK 确认,后 FIN 关闭),所以总共需要四次。
3.4 TIME_WAIT 状态的作用
客户端在发送最后一个 ACK 后进入 TIME_WAIT 状态,等待 2MSL 后才真正关闭连接。原因如下:
- 确保最后的 ACK 能够到达服务器。 如果该 ACK 丢失,服务器会重发 FIN,客户端在 TIME_WAIT 期间可以重新发送 ACK。
- 防止旧连接的延迟报文段干扰新连接。 等待 2MSL 可以确保网络中属于旧连接的报文段全部过期消失,避免被新连接误收。
四、TCP 连接状态转换总结
4.1 状态流转图
stateDiagram-v2
direction LR
[*] --> CLOSED: 初始状态
state "客户端状态" as ClientStates {
CLOSED --> SYN_SENT: 发送 SYN
SYN_SENT --> ESTABLISHED: 收到 SYN+ACK 并发送 ACK
ESTABLISHED --> FIN_WAIT_1: 发送 FIN
FIN_WAIT_1 --> FIN_WAIT_2: 收到 ACK
FIN_WAIT_2 --> TIME_WAIT: 收到 FIN
TIME_WAIT --> CLOSED: 等待 2MSL
}
state "服务器状态" as ServerStates {
CLOSED --> LISTEN: 开始监听
LISTEN --> SYN_RCVD: 收到 SYN 并发送 SYN+ACK
SYN_RCVD --> ESTABLISHED: 收到 ACK
ESTABLISHED --> CLOSE_WAIT: 收到 FIN 并发送 ACK
CLOSE_WAIT --> LAST_ACK: 发送 FIN
LAST_ACK --> CLOSED: 收到 ACK
}
4.2 各状态说明
| 状态 | 说明 |
|---|---|
| CLOSED | 初始状态,表示连接尚未建立或已完全关闭 |
| LISTEN | 服务器等待客户端的连接请求 |
| SYN_SENT | 客户端已发送 SYN,等待服务器确认 |
| SYN_RCVD | 服务器已收到 SYN 并回复 SYN+ACK,等待客户端确认 |
| ESTABLISHED | 连接已建立,双方可以进行数据传输 |
| FIN_WAIT_1 | 已发送 FIN,等待对方确认 |
| FIN_WAIT_2 | 已收到对方对 FIN 的确认,等待对方发送 FIN |
| CLOSE_WAIT | 已收到对方 FIN 并确认,等待本端应用层关闭连接 |
| LAST_ACK | 已发送 FIN,等待对方最后的 ACK 确认 |
| TIME_WAIT | 已发送最后 ACK,等待 2MSL 后关闭 |
五、常见问题
5.1 大量 CLOSE_WAIT 状态的原因
如果服务器端出现大量 CLOSE_WAIT 状态,通常说明应用层没有正确调用 close() 方法关闭连接。CLOSE_WAIT 是被动关闭方在收到 FIN 并回复 ACK 后的状态,需要应用程序主动关闭 socket 才能继续推进到 LAST_ACK。
5.2 大量 TIME_WAIT 状态的原因
如果客户端出现大量 TIME_WAIT 状态,通常是因为短连接过多,连接频繁建立和关闭。可以通过以下方式优化:
- 使用长连接(连接池)减少频繁建连和断连。
- 调整内核参数
tcp_tw_reuse允许复用 TIME_WAIT 状态的连接。 - 缩短 MSL 值(不推荐在生产环境随意修改)。
5.3 SYN Flood 攻击
SYN Flood 是一种利用三次握手机制的拒绝服务攻击。攻击者向服务器发送大量伪造源地址的 SYN 报文,服务器回复 SYN+ACK 后永远收不到第三次握手的 ACK,导致半连接队列被占满,无法响应正常请求。
常见防御措施包括:
- 启用 SYN Cookie 机制。
- 增大半连接队列容量。
- 缩短 SYN+ACK 的重试次数和超时时间。
- 部署防火墙或流量清洗设备进行过滤。
六、总结
| 过程 | 次数 | 目的 | 发起方 |
|---|---|---|---|
| 三次握手 | 3 次 | 建立连接,确认双方收发能力,同步初始序列号 | 客户端 |
| 四次挥手 | 4 次 | 释放连接,各自独立关闭全双工的两个方向 | 任意一方均可发起 |
三次握手和四次挥手是 TCP 协议实现可靠传输的核心机制。理解这两个过程及其背后的状态转换,对于网络编程、故障排查和性能调优都具有重要意义。