🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏传送门:
❄️《数据结构》 ❄️《AI与Agent那些事》
❄️《从0开始学计算机网络》 ❄️《后端开发》

你有没有遇到过这种情况:给一个很久没联系的朋友打电话,接通之后,两个人总要来回确认几句——
"喂,听得见吗?"
"听得见听得见,你呢?"
"我也听得见,那咱说正事。"
这三句话,仔细想想很有意思。第一句是你在确认对方能不能听到你,第二句是对方在告诉你"我能听到你"的同时,也在确认你能不能听到他,第三句是你告诉对方"我也能听到你"。三句话说完,双方才确认了一个事实:我们俩都能听到彼此。然后才开始说正事。
你可能会觉得这太啰嗦了。但如果你把这套逻辑搬到计算机网络上,会发现一个惊人的事实:TCP 协议在建立连接的时候,干的事和这通电话一模一样,而且它也必须这么干。这就是我们今天要聊的"三次握手"。
先搞懂一件事:TCP 连接到底是什么?
在聊三次握手之前,得先弄清楚一个更基础的问题:TCP 连接到底是个什么东西?
很多人第一次听到"连接"这个词,脑子里会浮现出一根物理的线,像充电线一样把两台电脑插在一起。不是的。 TCP 连接不涉及任何物理线路,它更像是一份双方约定好的协议——就像你和朋友约好:"接下来我们聊天,每句话前面都加个编号,这样万一有句话没听清,你可以告诉我'第几句我没收到',我就重说一遍。"
这个"编号"的约定,就是 TCP 连接的核心
那为什么需要"建立连接"这个过程?想象一下,如果没有这个约定,你上来就噼里啪啦说一堆,对方可能根本没准备好接收,或者对方压根不想跟你说话,又或者你们俩对"第一句话"的编号理解不一致——那就全乱套了。
所以建立连接要解决三个问题:
1. 双方都同意:我愿意跟你通信,你也愿意跟我通信。
2. 双方都准备好:我有能力接收你的数据,你也有能力接收我的数据。
3. 双方都知道对方的初始状态:这里的"状态"指的就是序列号——你发的第一句话编号是多少,我发的第一句话编号是多少。
前两点好理解,第三点需要展开说说。在 TCP 的世界里,每个数据包都有一个编号,叫序列号(Sequence Number,简称 seq)。这个编号的作用是让接收方知道"这是你发的第几个数据",从而能按顺序拼装数据,也能准确地告诉发送方"我收到了你发的哪个编号之前的所有数据"。
这个"我收到了哪些"的反馈,就是确认号(Acknowledgment Number,简称 ack)。确认号的规则是:ack = 对方发来的 seq + 1,意思是"你发的编号为 seq 的那个包我收到了,下一个请发 seq+1"。
用人话说:seq 是"我这句是第几句",ack 是"你那句的第几句我收到了"。
有了这两个概念,三次握手就好理解了。
三次握手,一步一步来(SYN 和 ACK 的诞生)
现在我们来看三次握手到底是怎么进行的。还是用打电话的类比,但这次我们引入两个角色:客户端(主动发起连接的一方,比如你的浏览器)和服务端(被动等待连接的一方,比如网站服务器)。
假设客户端想跟服务端建立连接,它的初始序列号是 100,服务端的初始序列号是 300(实际是随机的,这里简化)。
第一次握手:客户端 → 服务端
客户端说:"喂,我是客户端,我想跟你建立连接。我的第一句话编号是 100。"
在 TCP 里,这个请求叫 SYN(Synchronize,同步的意思,表示"我要同步序列号")。这个包的特点是:SYN 标志位为 1,seq = 100,不携带任何数据。
第二次握手:服务端 → 客户端
服务端收到后,回复两件事:
第一件事是确认:"我收到你的编号 100 了,下一个请发 101。" 这个信息放在 ack = 101 里。
第二件事是表态:"我也想跟你建立连接,我的第一句话编号是 300。" 这个信息放在 seq = 300里,同时 SYN 标志位也是 1。
所以第二次握手,服务端发的包是 SYN + ACK,两个标志位都是 1,seq = 300,ack = 101。
这个包相当于电话里的第二句话:"我听得见你!我也要跟你说话,我的编号是 300。"
第三次握手:客户端 → 服务端
客户端收到后,需要确认两件事:
第一,服务端收到了自己的 SYN(因为服务端的 ack = 101 正确);
第二,服务端也想建立连接,且它的初始编号是 300。
于是客户端回复最后一个包:ACK,ack = 301(意思是"你发的编号 300 我收到了,下一个请发 301"),seq = 101(因为客户端上一句的编号是 100,这一句自然就是 101)。
这第三句话相当于电话里的:"我也听到你了!那我们开始吧。"

三次握手完成后,连接就建立了。之后客户端发的第一个数据包,seq 就是 101,服务端发的第一个数据包,seq 就是 301。双方对彼此的编号了如指掌,后续的数据传输就有了明确的对账依据。
如果你用 Wireshark 抓包工具抓一次网页访问的流量,在过滤栏输入 `tcp.flags.syn == 1`,你会看到最开始的三个包,标志位分别是 `SYN`、`SYN,ACK`、`ACK`,seq 和 ack 的数值虽然和你看到的随机数不一样,但规律完全一致。

为什么不是两次?——两次握手的致命缺陷
讲完三次握手的过程,一个很自然的问题就冒出来了:既然第一次握手客户端说了"我要连接",第二次握手服务端回了"我同意",那这不就够了吗?为什么还要第三次?
直觉上,两次确实看起来够。但 TCP 的设计者们考虑了一个非常刁钻的场景——网络中滞留的连接请求。
想象这样一个场景:客户端给服务端发了一个 SYN 请求(第一次握手),但这个包在网络上堵车了,迟迟没到。客户端等了一会儿没收到回复,以为包丢了,于是重发了一个 SYN。这一次,包顺利到达,服务端回复了 SYN+ACK,客户端也回复了 ACK,连接建立,数据传输完毕,正常关闭。
到目前为止一切正常。但问题来了:之前那个堵在路上的 SYN 并没有消失,它只是迟到。过了一段时间,它终于到达了服务端。
如果只有两次握手,会发生什么?
服务端收到这个迟到的 SYN,它无法判断这是不是一个"过期的请求"。在它看来,这就是一个正常的连接请求。于是它回复 SYN+ACK,然后进入"等待连接"的状态,分配了内存、端口、缓冲区等资源
但客户端那边呢?客户端知道这个 SYN 是自己早就发过的,而且连接已经关闭了,所以它收到服务端的 SYN+ACK 后,根本不会理会。于是服务端就傻傻地等在那里,等一个永远不会来的 ACK,白白占着资源。
如果这种情况频繁发生(比如网络状况不好),服务端的资源就会被大量无效的半开连接耗尽,别的正常请求反而进不来了。这就是经典的 SYN 洪泛攻击 的原理。

用打电话来类比就是:你发短信问朋友"在吗?",朋友回了"在"。但这条回复因为信号问题,你根本没收到。在两次握手的模型下,朋友会默认你已经知道他"在"了,于是开始自顾自地说话。而实际上你那边一无所知。
两次握手无法解决"已失效的连接请求突然到达"这个问题。因为服务端永远无法确认"客户端是否收到了我的 SYN+ACK"。只有第三次握手,客户端主动告诉服务端"我收到你的确认了",服务端才能确定这个连接是双方都认可的,才敢真正分配资源。
为什么不是四次?——三次已经够用
那四次呢?既然三次是为了确保"客户端确认了服务端的确认",那为什么不能更保险一点,来四次、五次?
答案是:三次已经完成了双向确认,第四次没有任何新增信息。
我们来拆解一下。如果设计成四次握手,流程会是这样:
1. 客户端 → 服务端:SYN(我要连接)
2. 服务端 → 客户端:ACK(我收到你的 SYN 了)
3. 服务端 → 客户端:SYN(我也想连接)
4. 客户端 → 服务端:ACK(我收到你的 SYN 了)
看出来了吗?第 2 步和第 3 步,服务端是在连续发两个包。但这两个包的信息——"我收到了你的请求"和"我也想建立连接"——本来就是两个独立的信息,完全可以合并到一个包里发送。
这就是为什么真实的 TCP 第二次握手是 SYN+ACK:把"确认对方的 SYN"和"发送自己的 SYN"这两个动作打包在一起。这就像你打电话时说的"我听得见,我也要跟你说话"——两件事一口气说完,没必要拆成两句。
那为什么不能把三次也合并成两次?因为第二次握手的 SYN+ACK 是服务端同时确认和表态,但客户端必须在收到服务端的 SYN 之后,才能确认"服务端收到了我的 SYN"(因为服务端的 ack 字段里包含了这个信息)。这个确认动作只能由客户端发出,无法合并到前面的任何一步里。
所以从信息论的角度看,三次握手是最少必要次数:
- 第一次:客户端 → 服务端,"我要说话"
- 第二次:服务端 → 客户端,"我听到你了,我也要说"(同时完成两个确认)
- 第三次:客户端 → 服务端,"我也听到你了"
四次的话,多出来的那一次纯属重复劳动,白白多花一次网络往返的时间。
说到往返时间,有个专业术语叫 RTT(Round-Trip Time,往返时间),指一个包从发送方到接收方再回到发送方所需的时间。三次握手需要 1.5 个 RTT:第一次握手花 0.5 个 RTT 到达服务端,第二次握手花 0.5 个 RTT 回到客户端(累计 1 个 RTT),第三次握手再花 0.5 个 RTT 到达服务端(累计 1.5 个 RTT)。四次握手则需要 2 个 RTT。在网络延迟高的场景下(比如跨洋访问),这 0.5 个 RTT 可能就是几十毫秒的差别。

总结
三次握手的本质,用一句话概括就是:用最少的网络往返次数,让通信双方确认彼此都同意、都准备好、且都知道对方的初始序列号。
第一次握手,客户端表达意愿;第二次握手,服务端确认意愿并表达自己的意愿;第三次握手,客户端确认服务端的意愿。每一步都不可省略,每一步都不可合并,三次不多不少刚好够用。
那反向的问题来了:建立连接要三次握手,那断开连接为什么需要四次挥手?这背后又有什么讲究?下次可以接着聊。如果你手边有 Wireshark,不妨现在就抓一次包,亲眼看看这三个包长什么样——纸上得来终觉浅,绝知此事要躬行。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2503_94545876/article/details/164739838




