文章目录
- [TCP 四次挥手彻底解析:FIN、ACK、SEQ、半关闭与 TIME-WAIT](#TCP 四次挥手彻底解析:FIN、ACK、SEQ、半关闭与 TIME-WAIT)
- 一、四次挥手前必须掌握的核心规则
-
- [1. TCP 是全双工协议](#1. TCP 是全双工协议)
- [2. FIN 表示本端不再发送数据](#2. FIN 表示本端不再发送数据)
- [3. FIN 会消耗一个序号](#3. FIN 会消耗一个序号)
- [4. 纯 ACK 不消耗新的序号](#4. 纯 ACK 不消耗新的序号)
- [5. ACK 标志位和 ack 确认号不是同一个概念](#5. ACK 标志位和 ack 确认号不是同一个概念)
- [二、变量 U、V、W 的准确含义](#二、变量 U、V、W 的准确含义)
-
- [U:客户端 FIN 占用的序号](#U:客户端 FIN 占用的序号)
- V:服务器发送第二次挥手时的当前序号
- [W:服务器发送 FIN 时的序号](#W:服务器发送 FIN 时的序号)
- [三、TCP 四次挥手完整过程](#三、TCP 四次挥手完整过程)
- [四、第一次挥手:客户端发送 FIN](#四、第一次挥手:客户端发送 FIN)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 序号](#3. 序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- [6. 第一次挥手表达的含义](#6. 第一次挥手表达的含义)
- [五、第二次挥手:服务器确认客户端 FIN](#五、第二次挥手:服务器确认客户端 FIN)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 序号](#3. 序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- [6. 第二次挥手表达的含义](#6. 第二次挥手表达的含义)
- 六、为什么第二次和第三次挥手不能总是合并
- [七、第三次挥手:服务器发送 FIN](#七、第三次挥手:服务器发送 FIN)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 序号](#3. 序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- [6. 第三次挥手表达的含义](#6. 第三次挥手表达的含义)
- [八、第四次挥手:客户端确认服务器 FIN](#八、第四次挥手:客户端确认服务器 FIN)
-
- [1. 报文方向](#1. 报文方向)
- [2. 标志位](#2. 标志位)
- [3. 序号](#3. 序号)
- [4. 确认号](#4. 确认号)
- [5. 状态变化](#5. 状态变化)
- 九、四次挥手字段汇总
- 十、代入具体数值理解
- [十一、为什么主动关闭方需要 TIME-WAIT](#十一、为什么主动关闭方需要 TIME-WAIT)
-
- [1. 保证最终 ACK 可以重新发送](#1. 保证最终 ACK 可以重新发送)
- [2. 让旧连接中的重复报文在网络中消失](#2. 让旧连接中的重复报文在网络中消失)
- [十二、为什么等待时间是 2MSL](#十二、为什么等待时间是 2MSL)
- [十三、为什么 TIME-WAIT 通常出现在主动关闭方](#十三、为什么 TIME-WAIT 通常出现在主动关闭方)
- 十四、四次挥手一定正好有四个报文吗
-
- [1. 第二次和第三次挥手可能合并](#1. 第二次和第三次挥手可能合并)
- [2. 报文重传会使实际数量超过四个](#2. 报文重传会使实际数量超过四个)
- [3. 双方可能同时关闭](#3. 双方可能同时关闭)
- 十五、四次挥手中的丢包与重传
-
- [1. 第一次 FIN 丢失](#1. 第一次 FIN 丢失)
- [2. 第二次 ACK 丢失](#2. 第二次 ACK 丢失)
- [3. 第三次 FIN 丢失](#3. 第三次 FIN 丢失)
- [4. 第四次 ACK 丢失](#4. 第四次 ACK 丢失)
- [十六、FIN 和 RST 的区别](#十六、FIN 和 RST 的区别)
- [十七、CLOSE-WAIT 过多通常意味着什么](#十七、CLOSE-WAIT 过多通常意味着什么)
- 十八、常见错误逐项修正
-
- [错误一:把 FIN 写成 F2N](#错误一:把 FIN 写成 F2N)
- [错误二:第一次挥手 ACK 通常为 0](#错误二:第一次挥手 ACK 通常为 0)
- [错误三:SEQ=U 是最后一个数据字节序号](#错误三:SEQ=U 是最后一个数据字节序号)
- [错误四:第二次挥手不需要 ack](#错误四:第二次挥手不需要 ack)
- [错误五:W 一定等于 V](#错误五:W 一定等于 V)
- [错误六:第四次挥手后客户端立即 CLOSED](#错误六:第四次挥手后客户端立即 CLOSED)
- [错误七:TIME-WAIT 是为了等待服务器确认最终 ACK](#错误七:TIME-WAIT 是为了等待服务器确认最终 ACK)
- [错误八:客户端一定进入 TIME-WAIT](#错误八:客户端一定进入 TIME-WAIT)
- 错误九:四次挥手一定是四个网络包
- 十九、完整状态迁移
- 二十、最终结论
TCP 四次挥手彻底解析:FIN、ACK、SEQ、半关闭与 TIME-WAIT
前言
TCP 建立连接时通常使用三次握手,而关闭连接时通常需要四次报文交换,这个过程一般称为:
TCP 四次挥手,Four-Way Handshake。
典型流程如下:
text
客户端 服务器
ESTABLISHED ESTABLISHED
| |
| -------- FIN + ACK -------------------> |
| <----------- ACK ---------------------- |
| |
| <-------- FIN + ACK ------------------- |
| ------------ ACK ---------------------> |
| |
TIME-WAIT CLOSED
|
| 等待 2MSL
v
CLOSED
虽然这张图的整体结构并不复杂,但在解释具体字段时,经常出现以下错误:
- 把
FIN写成F2N; - 认为第一次挥手的
ACK通常为 0; - 把
SEQ=U解释为"最后一个数据字节的序号"; - 忽略第二次和第四次挥手中的确认号;
- 认为第四次挥手发送后客户端立即进入
CLOSED; - 认为 TCP 关闭连接在任何情况下都严格需要四个报文;
- 不理解为什么主动关闭方需要进入
TIME-WAIT。
本文假设:
客户端主动关闭连接,服务器被动关闭连接。
我们使用 U、V、W 表示双方关闭过程中的序号,并逐字段分析四次挥手。
一、四次挥手前必须掌握的核心规则
1. TCP 是全双工协议
一条 TCP 连接中存在两个相互独立的数据传输方向:
text
客户端 → 服务器
服务器 → 客户端
客户端不再向服务器发送数据,并不意味着服务器也已经没有数据需要发送。
因此,关闭 TCP 连接实际上需要分别关闭两个方向:
text
关闭客户端 → 服务器方向
关闭服务器 → 客户端方向
这正是 TCP 通常需要四次挥手,而不是像三次握手那样只交换三个报文的根本原因。
2. FIN 表示本端不再发送数据
当某一端发送:
text
FIN = 1
表达的不是:
text
整个 TCP 连接立即销毁。
而是:
text
本端已经没有更多数据需要发送,
请求关闭本端到对端的发送方向。
发送 FIN 后,本端通常不能再发送新的应用数据,但仍然可以继续接收对端发送的数据。
RFC 9293 明确规定,主动关闭方发送 FIN 后进入 FIN-WAIT-1,不再接受新的发送请求,但仍然允许继续接收数据;收到 FIN 的一端需要先确认该 FIN,直到本地应用也关闭连接后,才发送自己的 FIN。(RFC 编辑器)
这种状态称为:
半关闭,Half-Close。
3. FIN 会消耗一个序号
与 SYN 一样,FIN 虽然不是应用层数据,但会占用 TCP 序号空间中的一个位置。
TCP 报文段占用的序号长度可以表示为:
text
SEG.LEN = 数据长度 + SYN + FIN
其中:
text
SYN=1 时计入 1
FIN=1 时计入 1
RFC 9293 将 SEG.SEQ 定义为报文段的第一个序号,将 SEG.ACK 定义为接收端下一次期望收到的序号,并明确规定报文段长度需要将 SYN 和 FIN 计算在内。FIN 在序号空间中的位置位于该报文所携带数据之后。(RFC 编辑器)
假设发送一个不携带数据的 FIN:
text
SEQ = U
FIN = 1
那么对端确认时必须发送:
text
ack = U + 1
4. 纯 ACK 不消耗新的序号
如果一个 TCP 报文:
text
ACK = 1
数据长度 = 0
SYN = 0
FIN = 0
那么它不会占用新的序号空间。
例如:
text
SEQ = V
ACK = 1
ack = U + 1
发送完这个纯 ACK 后,本端下一次发送数据时仍然可以使用:
text
SEQ = V
只有以下内容会推动发送序号向前移动:
text
应用数据:按字节数推进
SYN:推进 1
FIN:推进 1
5. ACK 标志位和 ack 确认号不是同一个概念
为了避免混淆,本文采用以下写法:
text
ACK TCP 首部中的 ACK 标志位
ack TCP 首部中的确认号字段
例如:
text
ACK = 1
ack = U + 1
表示:
- ACK 标志位已经置 1;
- 确认号字段有效;
- 对端下一次期望收到的序号是
U+1。
确认号本质上表示:
接收端下一次期望收到的序号。
二、变量 U、V、W 的准确含义
为了避免将"数据序号"和"FIN 序号"混在一起,本文作如下约定。
U:客户端 FIN 占用的序号
假设第一次挥手是一个不携带应用数据的纯 FIN,则:
text
客户端第一次挥手:
SEQ = U
这里的 U 表示:
客户端发送 FIN 时所使用的序号。
因此,服务器确认该 FIN 时:
text
ack = U + 1
不能简单地把 U 描述为:
text
客户端最后一个数据字节的序号。
如果客户端最后一个数据字节的序号是 U-1,那么 FIN 才使用序号 U。
V:服务器发送第二次挥手时的当前序号
服务器确认客户端 FIN 时,假设该 ACK 不携带数据:
text
SEQ = V
因为纯 ACK 不消耗序号,所以服务器后续如果立即发送数据,仍然从 V 开始。
W:服务器发送 FIN 时的序号
服务器在第二次和第三次挥手之间可能继续发送剩余数据。
假设服务器从 V 开始又发送了 N 字节数据,那么:
text
W = V + N
服务器发送自己的 FIN 时:
text
SEQ = W
如果服务器没有发送任何剩余数据,则:
text
W = V
因此,使用不同的 V 和 W 可以清晰表达:
服务器在确认客户端 FIN 后,仍然可能继续发送数据。
三、TCP 四次挥手完整过程
假设关闭前:
text
客户端状态:ESTABLISHED
服务器状态:ESTABLISHED
完整过程如下:
text
客户端 服务器
ESTABLISHED ESTABLISHED
| |
| FIN=1, ACK=1 |
| SEQ=U, ack=V |
| ------------------------------------------------>|
| |
FIN-WAIT-1 CLOSE-WAIT
| |
| ACK=1 |
| SEQ=V, ack=U+1 |
|<-------------------------------------------------|
| |
FIN-WAIT-2 CLOSE-WAIT
| |
| 服务器可能继续发送剩余数据 |
|<-------------------------------------------------|
| |
| FIN=1, ACK=1 |
| SEQ=W, ack=U+1 |
|<-------------------------------------------------|
| |
TIME-WAIT LAST-ACK
| |
| ACK=1 |
| SEQ=U+1, ack=W+1 |
| ------------------------------------------------>|
| |
TIME-WAIT CLOSED
|
| 等待 2MSL
v
CLOSED
RFC 9293 给出的标准正常关闭示例同样采用了 FIN,ACK → ACK → FIN,ACK → ACK 的报文顺序,并展示了 FIN-WAIT-1、FIN-WAIT-2、CLOSE-WAIT、LAST-ACK、TIME-WAIT 和 CLOSED 等状态迁移。(RFC 编辑器)
四、第一次挥手:客户端发送 FIN
1. 报文方向
text
客户端 → 服务器
客户端主动发起关闭。
2. 标志位
典型正常关闭中的标志位是:
text
FIN = 1
ACK = 1
这里需要修正一个常见错误:
text
第一次挥手 ACK=0
这种写法并不适用于已经建立的正常 TCP 连接。
TCP 连接处于 ESTABLISHED 状态时,双方已经同步了序号空间,客户端发送 FIN 时通常仍然需要携带当前有效确认信息。因此,标准正常关闭示例中的第一次挥手是:
text
FIN + ACK
而不是:
text
只有 FIN,ACK=0
RFC 9293 的正常关闭示例中,主动关闭方发送的第一个关闭报文明确为 CTL=FIN,ACK。(RFC 编辑器)
3. 序号
假设 FIN 不携带数据:
text
SEQ = U
U 是客户端当前下一个可使用的发送序号,也是这个 FIN 占用的序号。
如果客户端在 FIN 报文中同时携带数据,那么 SEQ 实际上表示该报文中第一个数据字节的序号,而 FIN 位于这些数据之后。
例如:
text
SEQ = 1000
数据长度 = 100
FIN = 1
那么:
text
数据占用序号:1000~1099
FIN 占用序号:1100
对端确认号:1101
为简化四次挥手分析,本文假设 FIN 报文不携带应用数据,所以直接写作:
text
SEQ = U
4. 确认号
正常建立状态下,第一次挥手通常还会携带:
text
ack = V
这里的 V 表示客户端下一次期望从服务器收到的序号。
因此,完整报文可以写为:
text
FIN = 1
ACK = 1
SEQ = U
ack = V
5. 状态变化
客户端发送 FIN 后,从:
text
ESTABLISHED
进入:
text
FIN-WAIT-1
含义是:
text
客户端的 FIN 已经发送,
正在等待服务器确认该 FIN。
此时客户端不能再发送新的应用数据,但仍然可以继续接收服务器发送的数据。(RFC 编辑器)
6. 第一次挥手表达的含义
客户端实际上是在告诉服务器:
text
我已经没有新的数据需要发送。
客户端到服务器这个方向可以关闭。
我的 FIN 使用序号 U,
请通过 ack=U+1 对它进行确认。
但我仍然可以接收你发送的数据。
五、第二次挥手:服务器确认客户端 FIN
服务器收到客户端的 FIN 后,需要立即确认。
1. 报文方向
text
服务器 → 客户端
2. 标志位
第二次挥手通常是一个纯 ACK:
text
ACK = 1
FIN = 0
它只负责确认客户端的 FIN,并不一定表示服务器也准备立即关闭发送方向。
3. 序号
假设服务器当前下一个可用序号是 V:
text
SEQ = V
因为这个报文不携带数据,也没有 SYN 或 FIN,所以不会消耗序号。
服务器之后仍然可以从:
text
SEQ = V
开始发送剩余数据。
4. 确认号
客户端第一次挥手发送:
text
SEQ = U
FIN = 1
FIN 占用一个序号,因此服务器回复:
text
ack = U + 1
完整报文为:
text
ACK = 1
FIN = 0
SEQ = V
ack = U + 1
5. 状态变化
服务器收到客户端 FIN 后,从:
text
ESTABLISHED
进入:
text
CLOSE-WAIT
服务器发送 ACK 后,客户端收到这个 ACK,从:
text
FIN-WAIT-1
进入:
text
FIN-WAIT-2
状态含义分别是:
text
CLOSE-WAIT:
已经收到对端的关闭请求,
等待本地应用程序也关闭连接。
FIN-WAIT-2:
本端发送的 FIN 已经得到确认,
等待对端发送自己的 FIN。
6. 第二次挥手表达的含义
服务器是在告诉客户端:
text
你的 FIN 我已经收到。
客户端到服务器方向已经关闭。
我下一次期望你的序号是 U+1。
但是服务器到客户端方向暂时仍然可以发送数据。
收到 FIN 的 TCP 端点需要确认 FIN,但不会自动立即发送自己的 FIN;它通常需要等待本地应用完成剩余工作并主动关闭。(RFC 编辑器)
六、为什么第二次和第三次挥手不能总是合并
服务器收到客户端 FIN 时,只能说明:
text
客户端没有更多数据需要发送。
它不能说明:
text
服务器也已经没有数据需要发送。
服务器可能仍然需要:
- 处理客户端刚刚发送完的请求;
- 生成响应;
- 把发送缓冲区中的剩余数据发送出去;
- 等待应用程序调用关闭操作;
- 完成日志、协议尾部或业务结果发送。
因此,服务器先发送 ACK 确认客户端 FIN,然后继续发送数据。
等本地应用明确表示不再发送数据后,服务器才发送自己的 FIN。
这就是第二次和第三次挥手通常分开的原因。
七、第三次挥手:服务器发送 FIN
服务器完成剩余数据发送后,关闭自己的发送方向。
1. 报文方向
text
服务器 → 客户端
2. 标志位
第三次挥手通常为:
text
FIN = 1
ACK = 1
其中:
FIN=1:服务器不再发送新数据;ACK=1:确认号字段有效。
3. 序号
服务器发送 FIN 时:
text
SEQ = W
如果第二次和第三次挥手之间,服务器发送了 N 字节数据,那么:
text
W = V + N
如果没有发送数据,则:
text
W = V
因此不能无条件认为:
text
第三次挥手 SEQ 一定等于第二次挥手 SEQ。
它是否相等取决于服务器在此期间是否发送了数据。
4. 确认号
由于客户端发送 FIN 后不能继续发送新的应用数据,服务器仍然期望客户端的下一个序号为:
text
ack = U + 1
完整报文为:
text
FIN = 1
ACK = 1
SEQ = W
ack = U + 1
5. 状态变化
服务器发送 FIN 后,从:
text
CLOSE-WAIT
进入:
text
LAST-ACK
含义是:
text
服务器已经发送自己的 FIN,
现在等待客户端的最终确认。
RFC 9293 的状态机规定,处于 CLOSE-WAIT 的端点在本地用户关闭连接、并且之前所有待发送数据均已处理后发送 FIN,然后进入 LAST-ACK。(RFC 编辑器)
6. 第三次挥手表达的含义
服务器是在告诉客户端:
text
服务器剩余的数据已经发送完毕。
服务器到客户端方向也可以关闭。
我的 FIN 使用序号 W,
请通过 ack=W+1 对它进行确认。
八、第四次挥手:客户端确认服务器 FIN
客户端收到服务器 FIN 后,发送最终 ACK。
1. 报文方向
text
客户端 → 服务器
2. 标志位
第四次挥手通常为:
text
ACK = 1
FIN = 0
客户端第一次挥手时已经发送过自己的 FIN,因此第四次不需要再次设置 FIN。
3. 序号
客户端第一次挥手的 FIN 使用:
text
SEQ = U
由于 FIN 消耗一个序号,客户端当前下一个序号为:
text
SEQ = U + 1
因此第四次挥手通常写为:
text
SEQ = U + 1
4. 确认号
服务器第三次挥手发送:
text
SEQ = W
FIN = 1
因为 FIN 消耗一个序号,所以客户端确认:
text
ack = W + 1
完整报文为:
text
ACK = 1
FIN = 0
SEQ = U + 1
ack = W + 1
5. 状态变化
客户端发送最终 ACK 后,不会立即进入 CLOSED,而是进入:
text
TIME-WAIT
服务器收到最终 ACK 后,从:
text
LAST-ACK
进入:
text
CLOSED
客户端等待 2MSL 后,才从:
text
TIME-WAIT
进入:
text
CLOSED
因此,下面这句话并不准确:
text
客户端发送第四次挥手后,连接立即彻底关闭。
更准确的说法是:
服务器收到第四次挥手后可以删除该连接状态;客户端仍然需要在内核中保留一段时间的 TIME-WAIT 状态,等待 2MSL 后才最终进入 CLOSED。
九、四次挥手字段汇总
在以下假设下:
- 客户端主动关闭;
- 四个控制报文均不携带应用数据;
- 服务器可能在第二次和第三次挥手之间发送剩余数据;
完整字段如下:
| 次数 | 方向 | 标志位 | SEQ | ack | 状态变化 |
|---|---|---|---|---|---|
| 第一次 | 客户端 → 服务器 | FIN=1, ACK=1 |
U |
V |
客户端进入 FIN-WAIT-1 |
| 第二次 | 服务器 → 客户端 | ACK=1 |
V |
U+1 |
服务器进入 CLOSE-WAIT,客户端进入 FIN-WAIT-2 |
| 第三次 | 服务器 → 客户端 | FIN=1, ACK=1 |
W |
U+1 |
服务器进入 LAST-ACK |
| 第四次 | 客户端 → 服务器 | ACK=1 |
U+1 |
W+1 |
客户端进入 TIME-WAIT,服务器进入 CLOSED |
简化表示:
text
第一次挥手:
客户端 → 服务器
FIN=1, ACK=1, SEQ=U, ack=V
第二次挥手:
服务器 → 客户端
ACK=1, SEQ=V, ack=U+1
第三次挥手:
服务器 → 客户端
FIN=1, ACK=1, SEQ=W, ack=U+1
第四次挥手:
客户端 → 服务器
ACK=1, SEQ=U+1, ack=W+1
十、代入具体数值理解
假设关闭前:
text
客户端下一个发送序号:1000
客户端下一次期望服务器序号:5000
因此:
text
U = 1000
V = 5000
第一次挥手
客户端发送:
text
FIN = 1
ACK = 1
SEQ = 1000
ack = 5000
FIN 占用序号 1000,所以客户端后续序号变为:
text
1001
第二次挥手
服务器确认:
text
ACK = 1
SEQ = 5000
ack = 1001
这个 ACK 不消耗序号,因此服务器的下一个数据仍然可以从:
text
SEQ = 5000
开始。
服务器继续发送数据
假设服务器又发送了 200 字节数据:
text
SEQ = 5000
数据长度 = 200
这些数据占用:
text
5000~5199
服务器下一个序号变成:
text
5200
因此:
text
W = 5200
第三次挥手
服务器发送:
text
FIN = 1
ACK = 1
SEQ = 5200
ack = 1001
服务器 FIN 占用序号 5200。
第四次挥手
客户端确认:
text
ACK = 1
SEQ = 1001
ack = 5201
最终:
text
客户端进入 TIME-WAIT
服务器收到 ACK 后进入 CLOSED
十一、为什么主动关闭方需要 TIME-WAIT
TIME-WAIT 不是无意义地浪费连接资源,它主要解决两个问题。
1. 保证最终 ACK 可以重新发送
TCP 的第四次挥手是一个 ACK。
ACK 本身通常不会再被确认,因此客户端无法直接知道:
text
服务器是否真正收到了最终 ACK。
假设最终 ACK 丢失:
text
客户端 ---- 最终 ACK ----X----> 服务器
服务器仍然停留在:
text
LAST-ACK
等待确认。
超时后,服务器会重新发送 FIN:
text
服务器 -------- FIN --------> 客户端
如果客户端仍处于 TIME-WAIT,就可以再次发送:
text
ACK = 1
ack = W + 1
RFC 9293 规定,处于 TIME-WAIT 的端点收到对端重传的 FIN 时,需要再次确认该 FIN,并重新启动 2MSL 定时器。(RFC 编辑器)
如果客户端发送最终 ACK 后立即删除所有连接状态,那么服务器重传 FIN 时,客户端可能无法正确识别和确认该报文。
2. 让旧连接中的重复报文在网络中消失
TCP 连接通常由以下四元组标识:
text
源 IP
源端口
目的 IP
目的端口
一条连接关闭后,未来可能重新创建相同四元组的新连接。
如果旧连接中的延迟报文仍然存在于网络中,而新连接立即复用了相同四元组,那么旧报文可能干扰新连接。
TIME-WAIT 为旧重复报文提供足够时间,使其在网络中失效,降低旧连接报文进入新连接序号空间的风险。RFC 1337 将消除旧重复报文列为 TIME-WAIT 的核心功能之一。(IETF Datatracker2)
十二、为什么等待时间是 2MSL
MSL 是:
text
Maximum Segment Lifetime
最大报文段生存时间
它表示一个 TCP 报文段在网络中可能存活的最大时间。
等待 2MSL 可以覆盖一个可能的往返过程:
text
服务器重传 FIN:
服务器 → 客户端,最多经历 1MSL
客户端重新发送 ACK:
客户端 → 服务器,最多经历 1MSL
因此:
text
总等待时间 = 2MSL
这里的 2MSL 不是:
- 两倍 RTT;
- 固定等于某个统一秒数;
- 应用层配置的请求超时。
它是 TCP 为关闭阶段保留连接状态时使用的协议时间概念。RFC 9293 的正常关闭序列要求主动关闭端在 TIME-WAIT 中等待 2MSL。(RFC 编辑器)
十三、为什么 TIME-WAIT 通常出现在主动关闭方
在正常四次挥手中:
- 主动关闭方先发送 FIN;
- 被动关闭方确认 FIN;
- 被动关闭方发送自己的 FIN;
- 主动关闭方发送最终 ACK。
发送最终 ACK 的一方需要保留状态,以便:
- 对重传的 FIN 再次发送 ACK;
- 等待旧重复报文失效。
因此,正常情况下进入 TIME-WAIT 的通常是:
主动关闭连接的一方。
但是不能简单地死记:
text
客户端一定进入 TIME-WAIT。
真正决定 TIME-WAIT 归属的是:
谁负责发送关闭过程中的最终 ACK。
服务器也可以主动关闭连接。如果服务器先发送 FIN,那么服务器就可能成为进入 TIME-WAIT 的一方。
十四、四次挥手一定正好有四个报文吗
不一定。
"四次挥手"是 TCP 正常关闭过程的典型逻辑模型,不代表抓包时一定严格看到四个独立报文。
1. 第二次和第三次挥手可能合并
如果服务器收到客户端 FIN 时,服务器应用程序也已经没有数据需要发送,那么服务器可能将:
text
确认客户端 FIN 的 ACK
和:
text
服务器自己的 FIN
合并为一个:
text
FIN + ACK
此时抓包可能只看到三个主要关闭报文:
text
客户端 → 服务器:FIN + ACK
服务器 → 客户端:FIN + ACK
客户端 → 服务器:ACK
这并不表示协议逻辑缺少一次确认,只是:
第二次确认和第三次关闭请求被放进了同一个 TCP 报文段。
2. 报文重传会使实际数量超过四个
如果以下报文发生丢失:
- 第一次 FIN;
- 第二次 ACK;
- 第三次 FIN;
- 第四次 ACK;
TCP 都可能触发相应重传。
因此,抓包中可能看到:
text
5 个、6 个甚至更多关闭相关报文。
"四次"描述的是正常、无丢包、ACK 与 FIN 分开发送时的逻辑过程。
3. 双方可能同时关闭
如果客户端和服务器几乎同时执行关闭,双方都可能先发送 FIN。
此时双方都会进入:
text
FIN-WAIT-1
收到对方 FIN 但自己的 FIN 尚未被确认时,可能进入:
text
CLOSING
双方 FIN 均被确认后,再进入:
text
TIME-WAIT
RFC 9293 单独定义了 simultaneous close,即同时关闭场景,并展示了双方进入 CLOSING 和 TIME-WAIT 的状态过程。(RFC 编辑器)
因此,普通的:
text
FIN-WAIT-1 → FIN-WAIT-2 → TIME-WAIT
只适用于典型的单方主动关闭场景。
十五、四次挥手中的丢包与重传
1. 第一次 FIN 丢失
客户端停留在:
text
FIN-WAIT-1
超时后重新发送 FIN。
2. 第二次 ACK 丢失
客户端没有收到对自己 FIN 的确认,因此会重新发送 FIN。
服务器再次收到重复 FIN 后,重新发送 ACK。
3. 第三次 FIN 丢失
服务器停留在:
text
LAST-ACK
超时后重新发送 FIN。
4. 第四次 ACK 丢失
服务器没有收到最终确认,继续停留在:
text
LAST-ACK
超时后重新发送 FIN。
客户端仍在:
text
TIME-WAIT
因此可以再次确认服务器 FIN,并重新启动 2MSL 定时器。(RFC 编辑器)
这也是不能删除 TIME-WAIT 的重要原因之一。
十六、FIN 和 RST 的区别
TCP 连接不仅可以通过 FIN 正常关闭,也可能通过 RST 异常终止。
FIN:正常、优雅关闭
FIN 表示:
text
我已经没有更多数据需要发送,
但之前已经发送的数据仍应可靠到达。
特点包括:
- 通常需要确认;
- 支持半关闭;
- 允许对端继续发送剩余数据;
- 按照 TCP 状态机逐步关闭;
- 可能产生 TIME-WAIT。
RST:立即中止连接
RST 表示:
text
立即终止当前连接。
它通常出现在:
- 向不存在的端口建立连接;
- 向已经失效的连接发送报文;
- 应用或协议栈主动异常终止连接;
- 收到无法接受的连接状态报文。
RST 与 FIN 的语义完全不同:
text
FIN:我正常发送完了。
RST:这个连接立即作废。
因此,不能把收到 RST 的过程称为标准四次挥手。
十七、CLOSE-WAIT 过多通常意味着什么
当服务器收到客户端 FIN 后,会进入:
text
CLOSE-WAIT
此时 TCP 协议栈已经确认了客户端的 FIN,但服务器只有在本地应用程序关闭对应连接后,才能发送自己的 FIN 并进入 LAST-ACK。
因此,大量连接长期停留在 CLOSE-WAIT,通常意味着:
text
应用程序没有及时关闭已经收到 EOF 的连接。
常见原因包括:
- 读取到对端关闭后忘记调用关闭操作;
- 异常路径没有释放套接字;
- 线程被阻塞,无法执行清理逻辑;
- 连接对象仍然被错误引用;
- 业务处理迟迟没有结束。
需要区分:
text
大量 TIME-WAIT:
通常与主动关闭频繁、短连接较多有关。
大量 CLOSE-WAIT:
通常与被动关闭方的应用程序没有及时释放连接有关。
十八、常见错误逐项修正
错误一:把 FIN 写成 F2N
错误:
text
F2N
正确:
text
FIN
FIN 是 Finish 的缩写,用于表示发送方向结束。
错误二:第一次挥手 ACK 通常为 0
错误写法:
text
FIN = 1
ACK = 0
典型正常关闭应写为:
text
FIN = 1
ACK = 1
因为连接已经处于同步状态,FIN 报文通常还需要携带当前有效的累计确认号。RFC 9293 的正常关闭示例明确使用 FIN,ACK。(RFC 编辑器)
错误三:SEQ=U 是最后一个数据字节序号
不准确:
text
U 是客户端最后一个数据字节的序号。
准确表述:
text
在不携带数据的 FIN 报文中,
U 是 FIN 自己占用的序号。
如果最后一个数据字节序号是 U-1,那么 FIN 使用 U。
错误四:第二次挥手不需要 ack
错误。
第二次挥手需要确认客户端 FIN:
text
ack = U + 1
因为 FIN 占用一个序号。
错误五:W 一定等于 V
不一定。
如果服务器在第二次和第三次挥手之间发送了 N 字节数据:
text
W = V + N
只有服务器没有发送任何新数据时:
text
W = V
错误六:第四次挥手后客户端立即 CLOSED
错误。
客户端发送最终 ACK 后进入:
text
TIME-WAIT
等待 2MSL 后才进入:
text
CLOSED
服务器收到最终 ACK 后才直接进入 CLOSED。
错误七:TIME-WAIT 是为了等待服务器确认最终 ACK
错误。
服务器不会再发送一个"第五次挥手"来确认最终 ACK。
TIME-WAIT 的主要作用是:
- 处理服务器 FIN 的可能重传;
- 再次发送最终 ACK;
- 等待旧重复报文失效。
错误八:客户端一定进入 TIME-WAIT
错误。
更准确的说法是:
text
正常关闭过程中,发送最终 ACK 的一方通常进入 TIME-WAIT。
谁主动关闭,谁通常进入 TIME-WAIT。
客户端只是教学示例中经常被假设为主动关闭方。
错误九:四次挥手一定是四个网络包
错误。
实际抓包可能出现:
text
3 个报文:ACK 与 FIN 被合并
4 个报文:典型正常关闭
超过 4 个报文:存在重传或其他数据交互
"四次挥手"描述的是关闭两个传输方向所需的逻辑动作。
十九、完整状态迁移
客户端主动关闭
text
ESTABLISHED
|
| 发送 FIN
v
FIN-WAIT-1
|
| 收到对 FIN 的 ACK
v
FIN-WAIT-2
|
| 收到服务器 FIN,发送最终 ACK
v
TIME-WAIT
|
| 等待 2MSL
v
CLOSED
服务器被动关闭
text
ESTABLISHED
|
| 收到客户端 FIN,发送 ACK
v
CLOSE-WAIT
|
| 本地应用完成剩余工作并关闭
| 发送 FIN
v
LAST-ACK
|
| 收到最终 ACK
v
CLOSED
二十、最终结论
标准的 TCP 四次挥手可以概括为:
text
第一次挥手:
客户端 → 服务器
FIN=1, ACK=1, SEQ=U, ack=V
第二次挥手:
服务器 → 客户端
ACK=1, SEQ=V, ack=U+1
第三次挥手:
服务器 → 客户端
FIN=1, ACK=1, SEQ=W, ack=U+1
第四次挥手:
客户端 → 服务器
ACK=1, SEQ=U+1, ack=W+1
需要牢记以下规则:
text
1. TCP 是全双工协议,两个发送方向需要分别关闭。
2. FIN 表示本端不再发送数据,不代表整个连接立即销毁。
3. FIN 会占用一个序号,因此确认号需要加 1。
4. 纯 ACK 不占用新的序号。
5. 第一次挥手在正常已建立连接中通常是 FIN+ACK,而不是 ACK=0。
6. U 是客户端 FIN 占用的序号,不是最后一个数据字节的序号。
7. V 是服务器确认 FIN 时的当前序号。
8. W 是服务器发送自己的 FIN 时的序号。
9. 如果服务器继续发送了数据,那么 W 会大于 V。
10. 客户端发送最终 ACK 后进入 TIME-WAIT,而不是立即 CLOSED。
11. TIME-WAIT 用于处理 FIN 重传并隔离旧连接中的延迟报文。
12. 四次挥手是典型逻辑模型,实际抓包可能是三个、四个或更多报文。
理解四次挥手最关键的一句话是:
text
FIN 只关闭一个方向。
客户端说"我不再发送",
不等于服务器也已经发送完毕。
正因为 TCP 两个方向可以独立关闭,所以服务器需要先确认客户端的 FIN,完成剩余数据发送后,再单独发送自己的 FIN。这就是 TCP 通常需要四次挥手的根本原因。
该版本可以与前面的"三次握手"博客组成同一系列,变量、字段命名和讲解结构已经统一。
2: https://datatracker.ietf.org/doc/html/rfc1337 "
RFC 1337 - TIME-WAIT Assassination Hazards in TCP
"