前言

大家好,这里是程序员阿亮,今天也是在工位上写博客的一天~

不知道大家是否了解TCP呢,这是我们计算机网络在运输层的协议,用于确保我们的连接的可靠性,它是面向连接的协议

一、面向连接?

这属于一个小知识点了,所谓的面向连接,就举例子来说就是

想象一下打电话,你必须先拨号,等对方接听,确认彼此都能听清后,才能开始聊天;聊完后,还要互相道别才能挂断。

我们TCP也是,在全双工发送数据之前,要经历一个三次握手来相互连接,然后通信,再经历一个四次挥手来断开连接。

那么我们本文主要就是研究三次握手和四次挥手的详细流程!

二、TCP相关“暗号”

在开始之前,我们需要先认识几个 TCP 报文头部中的关键标志位(Flags)和序列号。它们就像是通信双方约定的“暗号”:

  • seq (Sequence Number,序列号):你可以理解为发件人给这封信编的“页码”,用来保证数据的顺序,防止乱序。

  • ack (Acknowledgment Number,确认号):告诉对方“你发给我的第 X 页我已经收到了,我现在期待你发第 X+1 页”。

  • SYN (Synchronize):同步标志。当 SYN=1 时,表示“我想和你建立连接”。

  • ACK (Acknowledge):确认标志。当 ACK=1 时,表示“我收到了你的消息,确认无误”。(注意:标志位 ACK和确认号 ack 是两个概念,通常配合使用)。

  • FIN (Finish):结束标志。当 FIN=1 时,表示“我的数据发完了,我想断开连接”。

三、建立连接:三次握手 (Three-way Handshake)

1. 通俗比喻:一次成功的双向确认

想象你要和远方的朋友用对讲机通话:

  • 你(客户端):“呼叫洞幺,我是洞两,你能听到吗?” (第一次握手)

  • 朋友(服务端):“洞幺收到,我能听到你。你能听到我吗?” (第二次握手)

  • 你(客户端):“洞两收到,我也能听到你。” (第三次握手)

  • —— 确认双方的麦克风和喇叭都没问题,开始正式聊天。

2. 技术拆解:底层发生了什么?

在开始前,服务器必须启动并监听某个端口(处于 LISTEN 状态),等待客户端的连接。

  • 第一次握手(客户端 -> 服务端):
    客户端随机生成一个初始序列号 seq = x,并将标志位 SYN = 1。将这个数据包发送给服务端。
    意义:客户端说:“我想和你建连,我的初始序列号是 x。”
    状态:客户端进入 SYN_SENT(同步已发送)状态。

  • 第二次握手(服务端 -> 客户端):
    服务端收到请求后,如果同意连接,也会随机生成自己的初始序列号 seq = y。同时,为了回应客户端,它把 ack 设置为 x + 1,并将标志位 SYN = 1 和 ACK = 1。将这个数据包发给客户端。
    意义:服务端说:“收到你的建连请求了(期待你的下个包 x+1),我也想和你建连,我的初始序列号是 y。”
    状态:服务端进入 SYN_RCVD(同步收到)状态。

  • 第三次握手(客户端 -> 服务端):
    客户端收到服务端的同意后,还要给服务端发个回执。把 ack 设置为 y + 1,并将标志位 ACK = 1。
    意义:客户端说:“收到你的同意了(期待你的下个包 y+1),我们开始传数据吧!”
    状态:客户端进入 ESTABLISHED(已建立连接)状态。服务端收到后,也进入 ESTABLISHED 状态。

深度思考:为什么一定要是“三”次?两次不行吗?四次呢?

这是面试中出场率高达 99% 的问题。

  • 为什么不是两次?
    核心是为了防止历史的失效连接请求突然又传到了服务端
    假设客户端发了第一个 SYN 报文,但在网络中滞留了。客户端以为丢包了,于是重发了一个新的 SYN 并成功建立了连接、传输完数据后断开了。
    此时,那个滞留的旧 SYN 突然到达了服务端。如果是两次握手,服务端只要回发一个 SYN+ACK,服务端就单方面认为连接建立了,开始傻傻地等待客户端发数据,从而白白浪费服务器资源。
    有了第三次握手,客户端收到服务端的 SYN+ACK 后,会发现“咦,这是一个我早就放弃的旧连接”,于是会发送 RST 报文拒绝连接,避免资源浪费。

  • 为什么不是四次?
    其实第二次握手中的 SYN(服务端的建连请求)和 ACK(对客户端的确认)是可以分开发送的。但为了提高效率,TCP 协议把它们合并在同一个数据包里发送了,所以四次缩减成了三次。

四、断开连接:四次挥手 (Four-way Teardown)

数据传输完毕后,需要断开连接释放资源。因为 TCP 是全双工通信(即双方可以同时向对方发送数据),所以断开连接需要双方各自独立关闭自己那一半的通道,这就导致了需要四次挥手。

1. 通俗比喻:和平分手的室友

想象两个合租的室友 A (客户端) 和 B (服务端) 准备退房:

  • A(客户端):“我的东西收拾完了,我准备搬走了。” (第一次挥手)

  • B(服务端):“知道了。你等会儿,我还有几件衣服没收拾完。” (第二次挥手)
    (此时 A 不再放新东西,但会等待 B 收拾,B 继续收拾剩下的东西...)

  • B(服务端):“好了,我的东西也全部收拾完了,我也可以走了。” (第三次挥手)

  • A(客户端):“好的,那我们关门交钥匙吧,再见。” (第四次挥手)

2. 技术拆解:底层发生了什么?

  • 第一次挥手(主动方 -> 被动方):
    客户端(也可以是服务端主动)没有数据要发送了,发送一个 FIN = 1,序列号 seq = u 的数据包。
    意义:客户端说:“我的数据发完了,请求断开连接。”
    状态:客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(被动方 -> 主动方):
    服务端收到 FIN,知道客户端没数据要发了,于是回复一个 ACK = 1,确认号 ack = u + 1。
    意义:服务端说:“收到你的断开请求了。但我可能还有数据没发完,你先等我发完。”
    状态:服务端进入 CLOSE_WAIT 状态。客户端收到这个确认后,进入 FIN_WAIT_2 状态。(此时连接处于半关闭状态客户端不能发数据,但能收数据)。

  • 第三次挥手(被动方 -> 主动方):
    服务端的数据也发送完毕了,于是它也发送一个 FIN = 1,序列号 seq = w 的数据包给客户端。
    意义:服务端说:“我的数据也发完了,我也可以断开了。”
    状态:服务端进入 LAST_ACK(最后确认)状态。

  • 第四次挥手(主动方 -> 被动方):
    客户端收到服务端的 FIN 后,必须回复一个确认包 ACK = 1,ack = w + 1。
    意义:客户端说:“好的,收到你的断开信息,再见。”
    状态:客户端进入 TIME_WAIT 状态。服务端收到这个 ACK 后,立刻进入 CLOSED 状态,彻底关闭连接。客户端在等待了 2MSL(两倍最大报文段寿命)时间后,也进入 CLOSED 状态。

3. 为什么断开需要“四”次,而建立只需“三”次?

建立连接时,服务端的 ACK 和 SYN 是可以合并发送的(第二次握手)。
但断开连接时,由于 TCP 的半关闭特性,客户端说“我不发了”(FIN),服务端必须立刻回复“知道了”(ACK)。可是服务端可能还有一部分数据没传完,所以不能马上发送自己的 FIN。只有等服务端的数据真正传完了,才能再发送 FIN。
所以,服务端的 ACK 和 FIN 是分两次发送的,这就造成了四次挥手。

4. 为什么客户端第四次挥手后,还要等待一个 TIME_WAIT (2MSL) 才能关闭?

客户端发完最后一个 ACK 后,没有立刻拍屁股走人,而是耐心等待了 2MSL(通常为 1-4 分钟),原因有两个:

  1. 保证最后一个 ACK 能够顺利到达服务端:
    网络是不可靠的。如果客户端发出的最后那个 ACK 在路上丢了,服务端收不到确认,就会以为客户端没收到自己的 FIN,于是会超时重传那个 FIN。如果客户端不等待,直接关闭了,面对重传的 FIN 它就会回复 RST 报错报文,导致服务端无法正常关闭。有了 TIME_WAIT,客户端如果再次收到 FIN,就知道之前的 ACK 丢了,可以再次补发 ACK,帮助服务端正常关闭。

  2. 防止“幽灵数据包”干扰新连接:
    网络中可能会有迷路延迟的数据包。等待 2MSL(一个报文在网络中一来一回的最长时间),可以确保这次连接产生的所有旧数据包在网络中彻底消亡。这样,当我们在同一个端口上建立新的连接时,就不会收到上一次连接残留的“幽灵数据”了。

总结

TCP 的设计堪称计算机科学史上的一大杰作。
三次握手,通过最小的成本(三次通信),在不可靠的网络上建立了可靠的双向通信通道,防范了历史残留连接的干扰;
四次挥手,则完美兼顾了全双工通信的特点,确保了双方最后的数据都能完整发送和接收,并通过精妙的 TIME_WAIT 机制为整个通信画上圆满的句号。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐