计算机网络 TCP三次握手、四次挥手超详细流程【报文交换、状态变化】
TCP(传输控制协议)是互联网最重要的协议之一,它保证了数据的可靠、有序传输。连接建立时的“三次握手”和连接关闭时的“四次挥手”是其核心机制,涉及特定的报文交换和状态变化。
一、TCP 三次握手(Three-Way Handshake) - 建立连接
目的:同步双方的初始序列号(Sequence Number,简称 Seq),确认对方能够正常收发数据,建立双向连接。
状态变化详细过程:
- CLOSED -> LISTEN(服务器端):
- 服务器启动,调用
listen()
进入LISTEN
状态,准备接受连接请求。
- 服务器启动,调用
- SYN_SENT(客户端):
- 客户端调用
connect()
主动发起连接请求。 - 客户端发送一个
SYN
报文:SYN
标志位设置为1
。- 随机生成一个初始序列号
client_seq = x
。 - 无确认号(因为还没收到对方的任何序列号)。
- 客户端进入
SYN_SENT
状态(表示已发送 SYN,等待对方的 SYN+ACK)。
- 客户端调用
- LISTEN -> SYN_RCVD(服务器端):
- 服务器处于
LISTEN
状态,接收到客户端发来的SYN
报文。 - 服务器决定接受连接,并发送一个
SYN + ACK
报文:SYN
标志位设置为1
。ACK
标志位设置为1
。- 随机生成一个初始序列号
server_seq = y
。 - 将确认号
ack = x + 1
(因为客户端的 SYN 占用了序列号 x,所以服务器期望收到客户端发来的下一个数据字节的序列号是 x+1)。
- 服务器进入
SYN_RCVD
状态(表示已发送 SYN+ACK,等待客户的 ACK)。
- 服务器处于
- SYN_SENT -> ESTABLISHED(客户端):
- 客户端处于
SYN_SENT
状态,收到服务器发来的SYN + ACK
报文。 - 客户端发送一个
ACK
报文:ACK
标志位设置为1
。- 序列号
seq = x + 1
(因为客户端的 SYN 报文占用了序列号 x,所以下一个报文的序列号是 x+1)。 - 确认号
ack = y + 1
(因为服务器的 SYN 报文占用了序列号 y,所以客户端期望收到服务器发来的下一个数据字节的序列号是 y+1)。
- 客户端进入
ESTABLISHED
状态(表示连接已建立)。
- 客户端处于
- SYN_RCVD -> ESTABLISHED(服务器端):
- 服务器处于
SYN_RCVD
状态,收到客户端发来的ACK
报文。 - 服务器验证该 ACK 报文的确认号
ack
是否等于y + 1
。 - 验证通过后,服务器也进入
ESTABLISHED
状态(表示连接已建立)。
- 服务器处于
至此,双向通信通道建立完成。
二、TCP 四次挥手(Four-Way Handshake) - 关闭连接
目的:双方都确认对方不再发送数据,安全地终止双向连接。
状态变化详细过程:
- ESTABLISHED -> FIN_WAIT_1(主动关闭方 - 通常是客户端):
- 主动关闭方(A)调用
close()
或进程退出。 - A 发送一个
FIN
报文:FIN
标志位设置为1
。- 序列号
seq = u
(等于 A 要发送的下一个数据的序列号,但 FIN 消耗一个序列号)。
- A 进入
FIN_WAIT_1
状态(表示已发送 FIN,等待对方的 ACK 或 对方的 FIN)。
- 主动关闭方(A)调用
- ESTABLISHED -> CLOSE_WAIT(被动关闭方):
- 被动关闭方(B)处于
ESTABLISHED
状态,收到 A 发来的FIN
报文。 - B 知道自己该方向的数据发送通道将被关闭(应用程序会收到 EOF)。
- B 发送一个
ACK
报文:ACK
标志位设置为1
。- 序列号
seq = v
(B 自己的下一个序列号)。 - 确认号
ack = u + 1
(表示收到了 A 的 FIN 报文)。
- B 进入
CLOSE_WAIT
状态(表示收到对方的 FIN,自己的关闭过程开始,等待自己的应用程序通知关闭)。此时:A 到 B 的发送通道已关闭(A不再发送数据),但 B 到 A 的发送通道可能还有数据需要发送。
- 被动关闭方(B)处于
- FIN_WAIT_1 -> FIN_WAIT_2(主动关闭方):
- A 处于
FIN_WAIT_1
状态,收到 B 发来的ACK
报文。 - A 验证该 ACK 报文的确认号
ack
是否等于u + 1
。 - 验证通过后,A 进入
FIN_WAIT_2
状态(表示已收到对方对 FIN 的 ACK,等待对方发送 FIN)。
- A 处于
- CLOSE_WAIT -> LAST_ACK(被动关闭方):
- B 处于
CLOSE_WAIT
状态,完成了自己方向的数据发送(应用程序调用close()
)。 - B 发送一个
FIN
报文:FIN
标志位设置为1
。- 序列号
seq = w
(这个序列号可能等于v
(如果CLOSE_WAIT
期间 B 没有发数据)或大于v
(如果 B 发过数据))。 - 确认号
ack
仍然是u + 1
(因为在这个方向上 A 没有新数据)。
- B 进入
LAST_ACK
状态(表示自己已发送 FIN,等待对方最后的 ACK)。
- B 处于
- FIN_WAIT_2 -> TIME_WAIT(主动关闭方):
- A 处于
FIN_WAIT_2
状态,收到 B 发来的FIN
报文。 - A 发送一个
ACK
报文:ACK
标志位设置为1
。- 序列号
seq = u + 1
(之前的 FIN 报文序列号是 u)。 - 确认号
ack = w + 1
(确认 B 的 FIN 报文)。
- A 进入
TIME_WAIT
状态(也称为2MSL Wait
状态)。此状态会持续一段时间(通常为2 * Maximum Segment Lifetime (MSL)
,默认在 Linux 中是 60 秒)。
- A 处于
- LAST_ACK -> CLOSED(被动关闭方):
- B 处于
LAST_ACK
状态,收到 A 发来的ACK
报文。 - B 验证该 ACK 报文的确认号
ack
是否等于w + 1
。 - 验证通过后,B 关闭连接,进入
CLOSED
状态。
- B 处于
- TIME_WAIT -> CLOSED(主动关闭方):
- A 在
TIME_WAIT
状态等待2MSL
时间。 - 目的:
- 确保最后一个 ACK 报文送达 B: 如果 B 没有收到最后一个 ACK(它在 LAST_ACK 状态等待),会在超时后重发 FIN。处于 TIME_WAIT 的 A 收到这个重发的 FIN,会再次发送 ACK。
- 确保网络中所有旧的报文段消散: 防止具有相同四元组(源IP、源端口、目的IP、目的端口)的下一个新连接,错误地接收和处理上一个旧连接的残留报文。
2MSL
时间到,A 关闭连接,进入CLOSED
状态。
- A 在
至此,连接完全终止。
核心问题解答
为什么需要“三次”握手?两次握手不行吗?
不行。 原因主要有:
- 避免失效连接请求造成的资源浪费和历史连接问题:
- 场景: 假设客户端发送了第一个
SYN=1
请求(X),但因网络拥塞严重延迟。客户端超时未收到响应,会重发第二个SYN=1
请求(Y)。Y 成功到达,服务器响应SYN+ACK
,客户端也响应ACK
,完成三次握手建立连接。通信结束,连接关闭。 - 问题: 现在那个延迟了的初始 SYN 请求 X 终于到达了服务器。如果只有两次握手(服务器收到 SYN -> 响应 SYN+ACK -> 建立连接),服务器会误以为这是一个新的连接请求(历史连接),并立即为其分配资源(建立连接表项、分配缓冲区等)。
- 后果: 服务器资源被无意义占用(因为客户端此时根本不知道还有这个连接存在,也不会发送数据)。此外,如果这个“幽灵连接”被分配了新的序列号,也可能干扰后续真正的新连接(相同端口复用)。
- **解决:**三次握手中的第三次握手(ACK)就是客户端的明确确认信号。 当那个延迟的 SYN 包 X 到达服务器时,服务器会发送 SYN+ACK,但客户端已经关闭了最初的连接意图,它会忽略这个 SYN+ACK 或者发送 RST 拒绝。这样服务器就不会为这个无效请求建立连接。
- 场景: 假设客户端发送了第一个
- 初始序列号同步的双向确认:
- 通信双方都需要知道对方的初始序列号。两次握手只能确保客户端知道服务器的初始序列号(在服务器的 SYN+ACK 中),并确认服务器的发送能力。但服务器在第二次握手时发送的 SYN+ACK,并未得到客户端的确认(仅两次握手的话,服务器在第二步就认为连接建立了)。
- 问题: 服务器不知道客户端是否成功收到了自己的序列号
y
。如果这个 SYN+ACK 丢失了,服务器认为自己建立了连接(已发送SYN+ACK -> 等待ACK),但客户端还在等待 SYN+ACK(它没收到),双方状态不一致。 - **解决:**第三次握手的
**ACK**
报文明确告诉服务器:“我收到了你的初始序列号**y**
,我期望的下一个序列号是**y+1**
”。 这完成了服务器序列号的同步确认。
总结: 三次握手是保证可靠性、防止建立错误连接、且完成序列号双向同步确认的最小开销方案。
为什么需要“四次”挥手?
主要原因是:TCP 连接是全双工(Full-Duplex)的。
- 双向独立的关闭通道:
- 当客户端发送
FIN
时,表示它数据发送完毕(不再往服务器写数据),但服务器到客户端方向的数据发送通道并未立即关闭。 - 服务器收到
FIN
后,知道自己该方向的数据接收通道已关闭(收到EOF),但需要检查自己的应用程序是否还有数据要发送给客户端。如果还有数据要发送(比如最后要确认客户端请求操作成功的消息),服务器会在 CLOSE_WAIT 状态继续发送这些数据。 - 只有等待服务器自己也确定所有数据都发送完毕(应用层调用
close()
)时,它才会发送自己的FIN
来关闭“服务器->客户端”方向的发送通道。 - 因此,关闭连接需要四个步骤:
- A -> B 发送 FIN:关闭 A 的发送通道。
- B -> A 发送 ACK:确认收到 A 的 FIN。此时 B 的接收通道关闭(知道 A 不再发数据)。
- B -> A 发送 FIN:关闭 B 的发送通道。(这条 FIN 的发送时间独立于前面的 ACK)
- A -> B 发送 ACK:确认收到 B 的 FIN。
- 当客户端发送
- FIN 和 ACK 可能无法合并:
- 当 B 收到 A 的 FIN 时,它必须立即响应一个 ACK(这是协议要求)。但此时 B 的应用程序可能还未决定关闭/发送完自己的数据(处于 CLOSE_WAIT 状态),因此 B 的 FIN 报文无法像第二次握手(SYN+ACK)那样与此时的 ACK 合并发送。ACK 必须在收到 FIN 后立即发送,而 FIN 必须等 B 准备好关闭其发送端时才发送。
总结: 四次挥手是 TCP 全双工特性决定的。发送 FIN 意味着“我这边不准备再发送数据了”。两次挥手只能关闭一个方向(A->B),四次挥手才能彻底关闭两个独立的数据发送通道(A->B 和 B->A),确保数据完整性。