TCP 三次握手 (Three-Way Handshake):建立连接

三次握手的核心目标是同步双方的初始序列号 (Initial Sequence Number, ISN),并确认双方的发送和接收能力都正常。这个过程确保了在开始传输数据之前,双方都为可靠通信做好了准备。

我们可以把它想象成一个非常严谨的电话呼叫过程:

详细步骤:

  1. 第一次握手 (SYN): 客户端 -> 服务器

    • 动作: 客户端决定发起连接,它会向服务器发送一个特殊的 TCP 段,这个段的 SYN (Synchronize) 标志位被置为 1。同时,客户端会随机选择一个初始序列号 client_isn (例如,J),并将其放入 TCP 头部的序列号字段中。
    • 状态变化: 客户端进入 SYN_SENT 状态,开始等待服务器的确认。
    • 通话比喻: “喂,你是服务器吗?我想和你通话,我的对话编号从 J 开始。”
  2. 第二次握手 (SYN-ACK): 服务器 -> 客户端

    • 动作: 服务器收到客户端的 SYN 包后,如果同意建立连接,它会回复一个 TCP 段。这个段包含两个关键信息:
      1. 同意连接: SYN 标志位也被置为 1。
      2. 确认收到: ACK (Acknowledgement) 标志位被置为 1,并且 TCP 头部的确认号 (Acknowledgement Number) 字段被设置为 client_isn + 1 (即 J + 1)。这表示:“我已经收到了你编号为 J 的请求”。
      3. 自己的初始序列号: 服务器也会随机选择一个自己的初始序列号 server_isn (例如,K),放入序列号字段。
    • 状态变化: 服务器进入 SYN_RCVD 状态。
    • 通话比喻: “是的,我是服务器。我同意通话,我的对话编号从 K 开始。另外,我确认收到了你从 J 开始的请求。”
  3. 第三次握手 (ACK): 客户端 -> 服务器

    • 动作: 客户端收到服务器的 SYN-ACK 包后,会检查确认号是否正确(是否为 J + 1)。如果正确,客户端会再发送一个确认段。这个段的 ACK 标志位置为 1,并且确认号字段被设置为 server_isn + 1 (即 K + 1)。
    • 状态变化:
      • 客户端发送完这个 ACK 包后,立即进入 ESTABLISHED 状态,此时它认为连接已成功建立,可以开始发送数据了
      • 服务器收到这个 ACK 包后,也进入 ESTABLISHED 状态,准备好接收数据
    • 通话比喻: “好的,我确认了你的身份和编号。现在我们可以开始通话了。”

Mermaid 流程图 (三次握手):

Client Server 连接初始关闭 (CLOSED) 1. SYN (seq=J) 进入 SYN_RCVD 状态 2. SYN-ACK (seq=K, ack=J+1) 进入 ESTABLISHED 状态 3. ACK (seq=J+1, ack=K+1) 进入 ESTABLISHED 状态 连接建立成功,可以开始数据传输 Client Server

思考:为什么是“三次”握手,而不是两次或四次?

三次握手的最主要目的,正是为了确保客户端和服务器双方都具备可靠的发送和接收能力,即建立一个全双工的信道。可靠的数据传输本身更多依赖于后续的数据确认和重传机制,但握手阶段是建立这条双向通道的基石。

同学 A 需要与同学 B 进行沟通。

  1. 第一次握手 (SYN):A 对 B 说:“听得见吗?”
    • TCP视角:客户端发送 SYN 包。当服务器成功接收到这个包后,服务器可以得出结论:客户端的发送能力和服务器自身的接收能力是正常的。
  2. 第二次握手 (SYN-ACK):B 回答:“能听到(这是对A的ACK),那你听得到我说话吗?(这是自己的SYN)”
    • TCP视角:服务器发送 SYN-ACK 包。当客户端成功接收到这个包后,客户端可以得出结论:自己的发送和接收能力都正常,并且服务器的发送和接收能力也正常。
  3. 第三次握手 (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 是一个全双工的协议(双方可以同时收发数据),连接的断开也必须是双方都同意的。不能像打电话一样,一方挂了就全断了。这里更像是一个严谨的会议结束流程,需要双方都确认“我没话要说了”。

详细步骤:

  1. 第一次挥手 (FIN): 主动关闭方 -> 被动关闭方 (例如,客户端 -> 服务器)

    • 动作: 客户端决定不再发送数据了,它会发送一个 FIN (Finish) 标志位置为 1 的 TCP 段。它依然会包含一个序列号 seq=M
    • 状态变化: 客户端进入 FIN_WAIT_1 状态。此时,客户端不能再发送数据,但仍然可以接收数据
    • 会议比喻: 客户端说:“我的议题讲完了,我这边准备结束了。”
  2. 第二次挥手 (ACK): 被动关闭方 -> 主动关闭方 (服务器 -> 客户端)

    • 动作: 服务器收到了客户端的 FIN 包,它会立即回复一个 ACK 包,确认号为 ack=M+1
    • 状态变化:
      • 服务器进入 CLOSE_WAIT 状态。此时,服务器的应用程序被告知客户端已经关闭了发送通道。服务器仍然可以向客户端发送数据(如果还有未发完的数据)。
      • 客户端收到这个 ACK 后,进入 FIN_WAIT_2 状态,等待服务器也说“我讲完了”。
    • 会议比喻: 服务器说:“收到,我明白你讲完了。请稍等,我这边还有一些收尾工作要做。”
  3. 第三次挥手 (FIN): 被动关闭方 -> 主动关闭方 (服务器 -> 客户端)

    • 动作: 当服务器也完成了所有的数据发送后,它会发送一个 FIN 标志位置为 1 的包,序列号为 seq=N
    • 状态变化: 服务器发送完后,进入 LAST_ACK 状态,等待客户端的最后确认。
    • 会议比喻: 服务器说:“好了,我的收尾工作也做完了,我这边也可以结束了。”
  4. 第四次挥手 (ACK): 主动关闭方 -> 被动关闭方 (客户端 -> 服务器)

    • 动作: 客户端收到服务器的 FIN 包后,回复一个最后的 ACK 包,确认号为 ack=N+1
    • 状态变化:
      • 客户端发送完这个 ACK 后,进入 TIME_WAIT 状态。它会等待 2*MSL (Maximum Segment Lifetime, 最大报文段生存时间),以确保服务器收到了这个最后的 ACK,并处理网络中可能延迟的旧数据包。等待结束后,客户端才最终进入 CLOSED 状态。
      • 服务器收到这个 ACK 后,立即进入 CLOSED 状态,连接正式关闭。
    • 会议比喻: 客户端说:“好的,我确认你也结束了。大家可以离场了。”

Mermaid 流程图 (四次挥手):

Client Server 数据传输结束,连接处于 ESTABLISHED 状态 1. FIN (seq=M) 进入 FIN_WAIT_1 状态 2. ACK (ack=M+1) 进入 CLOSE_WAIT 状态 收到ACK后,进入 FIN_WAIT_2 状态 (服务器可能继续发送剩余数据...) 3. FIN (seq=N) 进入 LAST_ACK 状态 4. ACK (ack=N+1) 进入 TIME_WAIT 状态 (等待 2*MSL) 收到ACK后,进入 CLOSED 状态 (等待 2*MSL 后) 进入 CLOSED 状态 Client Server

思考:为什么是“四次”挥手?以及 2MSL 的等待之谜

与三次握手一样,四次挥手的设计也是为了在复杂网络环境下确保通信的严谨性。

1. 为什么是“四次”而不是“三次”?

关键在于 TCP 的半关闭 (Half-Close) 状态。TCP 连接是全双工的,关闭也需要双方独立进行。

  • 挥手1 (FIN): 主动方说:“我没数据要发了。”
  • 挥手2 (ACK): 被动方内核的 TCP 协议栈立即回应:“收到你的关闭请求了。” 这是一个内核级别的快速响应
  • 延迟的挥手3 (FIN): 此时,被动方的应用程序可能还有数据没有发送完。内核必须等待应用程序处理完所有数据,并调用 close() 后,才能发送自己的 FIN 包。这个 FIN 和之前的 ACK 之间可能存在时间差

正是因为第二次挥手的 ACK 和第三次挥手的 FIN 在大多数情况下是分开发送的,所以总共需要四次通信。这与握手不同,握手时的 SYNACK 可以合并在一个包里,因为服务器在收到 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 的连接、数据传输和断开过程,本质上是一个遵循严格规则的状态机。理解这个状态机,能帮助我们通过 netstatss 等工具,在出现问题时精准地定位连接处于哪个阶段。

核心状态解析
  • 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: 主动关闭方。收到对方对自己的 FINACK 后进入此状态。此时,己方的发送通道已关闭,但仍在等待对方发送 FIN 包。
  • CLOSE_WAIT: 被动关闭方。收到对方的 FIN 包并发送了 ACK 后进入此状态。这表示对方已经不发数据了,但己方可能还有数据需要发送,内核正在等待本地应用程序调用 close()
  • LAST_ACK: 被动关闭方。当被动关闭方也发送了自己的 FIN 包后,进入此状态,等待对方对这个 FIN 的最终 ACK
  • CLOSING: 罕见状态。如果双方同时发送 FIN 包(即同时决定关闭连接),则会进入此状态,等待对方的 ACK
  • TIME_WAIT: 主动关闭方。收到对方的 FIN 并发送了最后一个 ACK 后进入此状态。在此状态下等待 2MSL,以确保连接的可靠关闭。
TCP 状态转移图

下面的 Mermaid 图完整地展示了 TCP 连接从建立到关闭的全过程,以及各个状态之间的转移路径。

app listen() / server
receive SYN / send SYN,ACK
receive ACK
app close() / send FIN
app connect() / client, send SYN
receive SYN,ACK / send ACK
receive SYN / send SYN,ACK (simultaneous open)
app close() / send FIN
receive FIN / send ACK
receive ACK
receive FIN / send ACK
receive FIN / send ACK
app close() / send FIN
receive ACK
receive ACK
after 2MSL timeout
CLOSED
LISTEN
SYN_RCVD
ESTABLISHED
FIN_WAIT_1
SYN_SENT
CLOSE_WAIT
FIN_WAIT_2
CLOSING
TIME_WAIT
LAST_ACK
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐