4. TCP 四次挥手原理:FIN、ACK 标志位详解与 TIME_WAIT 状态分析
TCP 连接的建立需要三次握手,而连接的终止则需要四次挥手。这一过程涉及 FIN 和 ACK 两个关键标志位的协同工作,以及一个重要的状态——TIME_WAIT。理解四次挥手的原理,对于分析网络延迟、排查连接泄漏以及设计高并发服务器至关重要。
4.1 四次挥手流程概述
TCP 是全双工通信,因此连接必须由双方各自独立关闭。四次挥手的过程本质上是双方各自发送 FIN 报文并确认对方 FIN 的过程。假设客户端主动发起关闭,其标准流程如下:
- 第一次挥手(FIN=1, seq=u):客户端发送一个 FIN 报文,表示客户端不再发送数据,但仍可以接收数据。客户端进入 FIN_WAIT_1 状态。
- 第二次挥手(ACK=1, seq=v, ack=u+1):服务端收到 FIN 后,发送 ACK 确认。服务端进入 CLOSE_WAIT 状态。客户端收到 ACK 后进入 FIN_WAIT_2 状态。
- 第三次挥手(FIN=1, seq=w, ack=u+1):服务端处理完剩余数据后,发送 FIN 报文,表示服务端也不再发送数据。服务端进入 LAST_ACK 状态。
- 第四次挥手(ACK=1, seq=u+1, ack=w+1):客户端收到 FIN 后,发送 ACK 确认。客户端进入 TIME_WAIT 状态,等待 2MSL 后关闭。服务端收到 ACK 后进入 CLOSED 状态。
关键点: 第二次挥手和第三次挥手之间,服务端可能还有数据需要发送,因此 ACK 和 FIN 通常分开发送,这就是“四次”而非“三次”的原因。如果服务端在收到 FIN 时已经没有数据要发送,可以将 ACK 和 FIN 合并,此时退化为“三次挥手”。
4.2 FIN 与 ACK 标志位详解
TCP 报文头部的标志位(Flags)用于控制连接状态。在四次挥手中,FIN 和 ACK 是最核心的两个标志位。
| 标志位 | 全称 | 作用 | 在挥手过程中的行为 |
|---|---|---|---|
| FIN | Finish | 表示发送方已完成数据发送,请求关闭本方向的连接。 | 发送 FIN 后,发送方不能再发送数据,但可以继续接收数据,直到收到对方的 FIN。 |
| ACK | Acknowledgment | 确认收到对方的数据或控制报文。 | 用于确认收到的 FIN 报文。确认号(ack)为对方 FIN 报文的序列号 + 1。 |
序列号与确认号: 在挥手过程中,序列号(seq)和确认号(ack)的更新规则与数据传输一致。FIN 报文本身会消耗一个序列号,因此收到 FIN 后,确认号需要加 1。
4.3 四次挥手状态变迁图
下图展示了客户端主动关闭时,双方的状态迁移路径。理解这些状态是排查网络问题的基本功。
4.4 TIME_WAIT 状态深度分析
TIME_WAIT 是四次挥手中最容易被忽视但又最重要的状态。它出现在主动关闭连接的一方(通常是客户端),持续时间为 2MSL(Maximum Segment Lifetime,报文最大生存时间,通常为 2 分钟)。
4.4.1 TIME_WAIT 存在的必要性
- 确保最后一个 ACK 可靠到达: 如果服务端没有收到第四次挥手的 ACK,会重发 FIN。TIME_WAIT 状态允许客户端在 2MSL 内重新发送 ACK,避免服务端一直处于 LAST_ACK 状态而无法关闭。
- 防止旧连接报文干扰新连接: 在 2MSL 时间内,网络中可能还存在属于旧连接的延迟报文。TIME_WAIT 确保这些报文在网络中消亡,不会错误地出现在使用相同四元组(源IP、源端口、目的IP、目的端口)的新连接中。
常见问题: 在高并发服务器中,如果客户端(或服务端作为主动关闭方)频繁创建和关闭连接,会导致大量 TIME_WAIT 状态的 socket 堆积。这可能会耗尽系统资源(如端口号),导致无法建立新连接。解决方案包括:调整内核参数(如
net.ipv4.tcp_tw_reuse、net.ipv4.tcp_tw_recycle)或优化应用层连接复用策略。
4.4.2 TIME_WAIT 与 CLOSE_WAIT 对比
| 状态 | 出现位置 | 持续时间 | 常见原因 | 排查方向 |
|---|---|---|---|---|
| TIME_WAIT | 主动关闭连接的一方 | 2MSL(约 2 分钟) | 正常关闭连接后的标准状态 | 检查端口是否耗尽;考虑开启 tcp_tw_reuse |
| CLOSE_WAIT | 被动关闭连接的一方 | 不确定,直到应用层调用 close() |
应用层未及时关闭 socket | 检查代码中 socket 关闭逻辑;是否存在资源泄漏 |
4.5 四次挥手抓包验证
使用 Wireshark 或 tcpdump 可以直观地看到四次挥手的过程。以下是一个典型的抓包过滤表达式和结果解读:
# 抓取指定 IP 和端口的 TCP 流量
tcpdump -i eth0 host 192.168.1.100 and port 8080 -w handshake.pcap
# 在 Wireshark 中过滤四次挥手报文
tcp.flags.fin == 1 or tcp.flags.ack == 1
抓包结果中,你会看到以下四个关键报文:
- FIN:客户端发送,Flags 为
0x11(FIN + ACK)。 - ACK:服务端回复,Flags 为
0x10(ACK)。 - FIN:服务端发送,Flags 为
0x11(FIN + ACK)。 - ACK:客户端回复,Flags 为
0x10(ACK)。
实战技巧: 如果抓包中只看到三次挥手(即服务端的 FIN 和 ACK 合并为一个报文),说明服务端在收到客户端的 FIN 时已经没有待发送数据,这是一种优化行为,属于正常现象。
4.6 本章小结
四次挥手是 TCP 连接关闭的标准流程,其核心在于 FIN 和 ACK 标志位的配合使用。TIME_WAIT 状态虽然看似“多余”,但它是保证 TCP 可靠性和安全性的关键设计。在实际的链路测试验证中,通过抓包分析挥手过程,可以快速定位连接关闭异常、资源泄漏等问题。