深入解析TCP:三次握手、四次挥手与完整状态机
TCP 三次握手 (Three-Way Handshake):建立连接
三次握手的核心目标是同步双方的初始序列号 (Initial Sequence Number, ISN),并确认双方的发送和接收能力都正常。这个过程确保了在开始传输数据之前,双方都为可靠通信做好了准备。
我们可以把它想象成一个非常严谨的电话呼叫过程:
详细步骤:
-
第一次握手 (SYN): 客户端 -> 服务器
- 动作: 客户端决定发起连接,它会向服务器发送一个特殊的 TCP 段,这个段的
SYN(Synchronize) 标志位被置为 1。同时,客户端会随机选择一个初始序列号client_isn(例如,J),并将其放入 TCP 头部的序列号字段中。 - 状态变化: 客户端进入
SYN_SENT状态,开始等待服务器的确认。 - 通话比喻: “喂,你是服务器吗?我想和你通话,我的对话编号从 J 开始。”
- 动作: 客户端决定发起连接,它会向服务器发送一个特殊的 TCP 段,这个段的
-
第二次握手 (SYN-ACK): 服务器 -> 客户端
- 动作: 服务器收到客户端的
SYN包后,如果同意建立连接,它会回复一个 TCP 段。这个段包含两个关键信息:- 同意连接:
SYN标志位也被置为 1。 - 确认收到:
ACK(Acknowledgement) 标志位被置为 1,并且 TCP 头部的确认号 (Acknowledgement Number) 字段被设置为client_isn + 1(即J + 1)。这表示:“我已经收到了你编号为 J 的请求”。 - 自己的初始序列号: 服务器也会随机选择一个自己的初始序列号
server_isn(例如,K),放入序列号字段。
- 同意连接:
- 状态变化: 服务器进入
SYN_RCVD状态。 - 通话比喻: “是的,我是服务器。我同意通话,我的对话编号从 K 开始。另外,我确认收到了你从 J 开始的请求。”
- 动作: 服务器收到客户端的
-
第三次握手 (ACK): 客户端 -> 服务器
- 动作: 客户端收到服务器的
SYN-ACK包后,会检查确认号是否正确(是否为J + 1)。如果正确,客户端会再发送一个确认段。这个段的ACK标志位置为 1,并且确认号字段被设置为server_isn + 1(即K + 1)。 - 状态变化:
- 客户端发送完这个 ACK 包后,立即进入
ESTABLISHED状态,此时它认为连接已成功建立,可以开始发送数据了。 - 服务器收到这个 ACK 包后,也进入
ESTABLISHED状态,准备好接收数据。
- 客户端发送完这个 ACK 包后,立即进入
- 通话比喻: “好的,我确认了你的身份和编号。现在我们可以开始通话了。”
- 动作: 客户端收到服务器的
Mermaid 流程图 (三次握手):
思考:为什么是“三次”握手,而不是两次或四次?
三次握手的最主要目的,正是为了确保客户端和服务器双方都具备可靠的发送和接收能力,即建立一个全双工的信道。可靠的数据传输本身更多依赖于后续的数据确认和重传机制,但握手阶段是建立这条双向通道的基石。
同学 A 需要与同学 B 进行沟通。
- 第一次握手 (SYN):A 对 B 说:“听得见吗?”
- TCP视角:客户端发送
SYN包。当服务器成功接收到这个包后,服务器可以得出结论:客户端的发送能力和服务器自身的接收能力是正常的。- 第二次握手 (SYN-ACK):B 回答:“能听到(这是对A的ACK),那你听得到我说话吗?(这是自己的SYN)”
- TCP视角:服务器发送
SYN-ACK包。当客户端成功接收到这个包后,客户端可以得出结论:自己的发送和接收能力都正常,并且服务器的发送和接收能力也正常。- 第三次握手 (ACK):A 再次回答:“能听见。”
- TCP视角:客户端再次发送
ACK包。当服务器成功接收到这个包后,服务器最终确认:客户端的接收能力正常,且对方已收到了自己的SYN请求。理解TCP三次握手的过程,以及为什么是三次握手,得分别站在客户端以及服务器的视角进行看待.
至此,双方都确认了自己的信息能被对方接收,对方的信息自己也能接收,一条可靠的双工通道才算真正建立起来。
-
为什么两次握手不行?
如果只有前两次握手(A -> B, B -> A),服务器B向客户端A发送了自己的SYN请求后,它无法确定A是否收到了。如果B的这个包在网络中丢失了,B就会一直等待A的回应,而A此时可能已经单方面认为连接建立了。第三次握手,正是A给B的一个明确答复,打消了B的疑虑,使得连接的发起方和接收方都达成了“连接已建立”的共识。 -
还有一个经典场景:防止已失效的连接请求
考虑一种情况:客户端发出的第一个SYN请求因为网络延迟,被困在了某个路由器。客户端超时后,会重发一个新的SYN,这次成功与服务器建立了连接,完成了数据传输,然后关闭了连接。一段时间后,那个被困的、早已失效的旧SYN请求终于抵达了服务器。- 如果是两次握手:服务器收到这个旧
SYN后,会误以为是客户端又发起了一个新连接,于是发送SYN-ACK并为这个连接分配资源。但客户端此时早已“离线”,不会有任何回应。这导致服务器单方面地、无效地浪费了资源。 - 有了三次握手:服务器发送
SYN-ACK后,客户端会发现这个确认号是针对一个早已过期的连接,因此会发送一个RST(Reset) 包来中止这个错误的连接,从而避免了服务器资源的浪费。
- 如果是两次握手:服务器收到这个旧
因此,三次握手是保证连接双向畅通,并处理网络异常所必需的最少步骤。
TCP 四次挥手 (Four-Way Handshake):断开连接
由于 TCP 是一个全双工的协议(双方可以同时收发数据),连接的断开也必须是双方都同意的。不能像打电话一样,一方挂了就全断了。这里更像是一个严谨的会议结束流程,需要双方都确认“我没话要说了”。
详细步骤:
-
第一次挥手 (FIN): 主动关闭方 -> 被动关闭方 (例如,客户端 -> 服务器)
- 动作: 客户端决定不再发送数据了,它会发送一个
FIN(Finish) 标志位置为 1 的 TCP 段。它依然会包含一个序列号seq=M。 - 状态变化: 客户端进入
FIN_WAIT_1状态。此时,客户端不能再发送数据,但仍然可以接收数据。 - 会议比喻: 客户端说:“我的议题讲完了,我这边准备结束了。”
- 动作: 客户端决定不再发送数据了,它会发送一个
-
第二次挥手 (ACK): 被动关闭方 -> 主动关闭方 (服务器 -> 客户端)
- 动作: 服务器收到了客户端的
FIN包,它会立即回复一个ACK包,确认号为ack=M+1。 - 状态变化:
- 服务器进入
CLOSE_WAIT状态。此时,服务器的应用程序被告知客户端已经关闭了发送通道。服务器仍然可以向客户端发送数据(如果还有未发完的数据)。 - 客户端收到这个 ACK 后,进入
FIN_WAIT_2状态,等待服务器也说“我讲完了”。
- 服务器进入
- 会议比喻: 服务器说:“收到,我明白你讲完了。请稍等,我这边还有一些收尾工作要做。”
- 动作: 服务器收到了客户端的
-
第三次挥手 (FIN): 被动关闭方 -> 主动关闭方 (服务器 -> 客户端)
- 动作: 当服务器也完成了所有的数据发送后,它会发送一个
FIN标志位置为 1 的包,序列号为seq=N。 - 状态变化: 服务器发送完后,进入
LAST_ACK状态,等待客户端的最后确认。 - 会议比喻: 服务器说:“好了,我的收尾工作也做完了,我这边也可以结束了。”
- 动作: 当服务器也完成了所有的数据发送后,它会发送一个
-
第四次挥手 (ACK): 主动关闭方 -> 被动关闭方 (客户端 -> 服务器)
- 动作: 客户端收到服务器的
FIN包后,回复一个最后的ACK包,确认号为ack=N+1。 - 状态变化:
- 客户端发送完这个 ACK 后,进入
TIME_WAIT状态。它会等待 2*MSL (Maximum Segment Lifetime, 最大报文段生存时间),以确保服务器收到了这个最后的 ACK,并处理网络中可能延迟的旧数据包。等待结束后,客户端才最终进入CLOSED状态。 - 服务器收到这个 ACK 后,立即进入
CLOSED状态,连接正式关闭。
- 客户端发送完这个 ACK 后,进入
- 会议比喻: 客户端说:“好的,我确认你也结束了。大家可以离场了。”
- 动作: 客户端收到服务器的
Mermaid 流程图 (四次挥手):
思考:为什么是“四次”挥手?以及 2MSL 的等待之谜
与三次握手一样,四次挥手的设计也是为了在复杂网络环境下确保通信的严谨性。
1. 为什么是“四次”而不是“三次”?
关键在于 TCP 的半关闭 (Half-Close) 状态。TCP 连接是全双工的,关闭也需要双方独立进行。
- 挥手1 (FIN): 主动方说:“我没数据要发了。”
- 挥手2 (ACK): 被动方内核的 TCP 协议栈立即回应:“收到你的关闭请求了。” 这是一个内核级别的快速响应。
- 延迟的挥手3 (FIN): 此时,被动方的应用程序可能还有数据没有发送完。内核必须等待应用程序处理完所有数据,并调用
close()后,才能发送自己的FIN包。这个FIN和之前的ACK之间可能存在时间差。
正是因为第二次挥手的 ACK 和第三次挥手的 FIN 在大多数情况下是分开发送的,所以总共需要四次通信。这与握手不同,握手时的 SYN 和 ACK 可以合并在一个包里,因为服务器在收到 SYN 时,就知道自己也要建立连接,没有应用层的数据需要等待。
2. 为什么 TIME_WAIT 状态需要等待 2MSL?
TIME_WAIT 状态是主动关闭连接的一方(发送最后一个 ACK 的一方)进入的。等待 2 * MSL (Maximum Segment Lifetime, 报文最大生存时间) 是为了解决两个关键问题:
- 保证连接可靠关闭
第四次挥手(最后的ACK)可能会在网络中丢失。如果丢失,被动关闭方(服务器)会因为收不到确认而超时重传第三次揮手的FIN包。如果主动方(客户端)发送ACK后立即关闭,就无法接收到这个重传的FIN,导致服务器无法正常关闭。通过等待2MSL,客户端确保自己有足够的时间接收到重传的FIN,并再次发送ACK,从而帮助服务器可靠地关闭。
时间解释:
1MSL确保客户端发出的ACK能到达服务器,另一个MSL确保服务器重传的FIN能返回到客户端,一来一回,总共2MSL。
- 防止旧连接的延迟报文干扰新连接
假设一个连接(由{源IP, 源端口, 目的IP, 目的端口}四元组唯一标识)关闭后,马上又用完全相同的四元组建立了一个新连接。此时,网络中可能还存在上一个旧连接的、延迟抵达的数据包。如果没有TIME_WAIT状态,这个旧数据包可能会被新连接错误地接收,造成数据混乱。通过让这个四元组组合在2MSL时间内不可用,可以确保所有旧连接的报文都已经在网络中自然消失,从而保证新连接的纯净。
TCP 状态机详解 (TCP State Machine Explained)
TCP 的连接、数据传输和断开过程,本质上是一个遵循严格规则的状态机。理解这个状态机,能帮助我们通过 netstat 或 ss 等工具,在出现问题时精准地定位连接处于哪个阶段。
核心状态解析
CLOSED: 初始状态。这并非一个真实的状态,而是表示连接不存在的起点和终点。LISTEN: 服务器侧。服务器调用listen()后进入此状态,表示它正在监听指定的端口,等待客户端发起连接。SYN_SENT: 客户端侧。客户端调用connect(),发送SYN包后进入此状态,等待服务器的SYN-ACK响应。SYN_RCVD: 服务器侧。服务器收到客户端的SYN包并发送了SYN-ACK包后进入此状态,等待客户端最后的ACK。ESTABLISHED: 双方共有。三次握手成功后,客户端和服务器都进入此状态。这是数据可以双向自由传输的正常状态。FIN_WAIT_1: 主动关闭方。主动关闭的一方(调用close())发送FIN包后进入此状态,等待对方的ACK。FIN_WAIT_2: 主动关闭方。收到对方对自己的FIN的ACK后进入此状态。此时,己方的发送通道已关闭,但仍在等待对方发送FIN包。CLOSE_WAIT: 被动关闭方。收到对方的FIN包并发送了ACK后进入此状态。这表示对方已经不发数据了,但己方可能还有数据需要发送,内核正在等待本地应用程序调用close()。LAST_ACK: 被动关闭方。当被动关闭方也发送了自己的FIN包后,进入此状态,等待对方对这个FIN的最终ACK。CLOSING: 罕见状态。如果双方同时发送FIN包(即同时决定关闭连接),则会进入此状态,等待对方的ACK。TIME_WAIT: 主动关闭方。收到对方的FIN并发送了最后一个ACK后进入此状态。在此状态下等待2MSL,以确保连接的可靠关闭。
TCP 状态转移图
下面的 Mermaid 图完整地展示了 TCP 连接从建立到关闭的全过程,以及各个状态之间的转移路径。
更多推荐

所有评论(0)