计网小知识 为啥TCP是三次握手(three-way handshake) 而不是两次/四次呢?

605 阅读8分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

> 这个问题可以说是老生常谈了!大家面试时候是不是都被问过😀
> 当时大家都是怎么回答的呢?🤔 
> 另外 本文的整理与总结如果有啥问题 欢迎大佬们指出!

本文中的回答参考了

  • 小林的《图解网络》
  • 极客时间 趣谈网络协议
  • RFC793
  • 知乎上大神的回答

感谢前辈们分享的知识!

另外以下内容也参杂着很多个人的理解 如果大家有其他想法 欢迎交流!! 感谢!!

面试官:“来聊聊计算机网络 为啥TCP连接是三次握手 不是两次/四次呢?”

我:“...”

这个经典问题吧~

  • 之前看过图解网络中的内容

  • 谢希仁的计算机网络 对应这部分的内容

  • 也搜过知乎 看过大佬解读的RFC中的内容

先说下结论 分两个点(两次握手肯定是做不到以下两点咯)

  • 避免重复连接造成的资源浪费
    • 防止已经失效的连接请求报文段一段时间后又传到了服务端
  • 避免历史连接造成的网络混乱
    • 三次握手可以防止历史连接的发生(通过发送RST报文)
    • TCP是可靠传输的 需要三次握手来约定、确定双方的初始序列号(seq——sequence numbers))

另外需要首先注意 RFC 793指出的 TCP连接使用三次握手的首要原因(毕竟权威一些嘛~)

> The principle reason for the three-way handshake is to 
> prevent old duplicate connections from causing confusion.

避免旧的重复连接(历史连接)导致的网络混乱!

分析这两点之前 说下三次握手建立起连接的过程吧 画个简图

请添加图片描述 下面报文结构的图片来源于《图解网络》

第一次握手的SYN报文

图片

第二次握手服务端发送的SYN+ACK报文

图片

第三次握手客户端发送的ACK报文

图片

【1】避免重复连接造成的资源浪费

然后说下三次握手相较于两次握手 的性能提升 以及一些关键作用!

第一点:(参考谢希仁的计算机网络-避免失效的连接造成影响 小林的图解网路-避免资源浪费)

  • 客户端发出的第一个连接请求报文段没有丢失 但是在某个网络结点被长时间阻塞了。

  • 如果是两次握手 会发生什么情况呢?

    • 所以这个包就成为了一个不被需要的存在 然而 如果使用两次握手 服务端将不会知道这是一个重复的网络包!(两次握手)
    • 在延误了一段时间后 这个旧的连接请求抵达服务端 服务端会误以为这是客户端发送的一个新的连接请求(两次握手)
    • 然后服务端就会给客户端发出确认报文 之后建立新的连接(两次握手)
    • 所以使用两次握手会导致资源的大量浪费!
  • 同样遇到类似的这种情况 三次握手 要机智得多!

    • 其实看上图也可以清楚了 三次握手必须得在客户端向服务端发送那个最后的确认报文(ACK 确认应答号:server_sin + 1) 才能建立连接

    图片

【2】避免历史连接造成网络混乱

听起来雀氏是网络混乱更可怕啊哈哈

第二点:【首要原因】三次握手来确定双方的初始序列号 (知乎-依靠seq序列号来做可靠重传/接收 图解网路哦-三次握手同步客户端、服务端的初始序列号;避免历史连接)

  • 由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机

    • 三次握手可以防止旧的重复连接初始化造成的混乱

    参考RFC 793指出的 TCP连接使用三次握手的首要原因

    The principle reason for the three-way handshake is to prevent old duplicate connections from causing confusion.

    也就是防止旧的重复连接初始化 造成网络混乱

    用小林的图解网络中的一幅图来解释这个问题——

    图片

    • 当旧的数据包比新数据包先抵达服务端时 客户端会发送RST报文 告诉服务端“这个是我的历史连接 不用连接、分配资源了!”
  • 而知乎上的一位阿里的工程师持这样(与图解网路中还是不太一样的!)的观点——三次握手可以确定客户端、服务端的初始seq序列号(也就是ISN)所以可以防止历史连接造成的混乱

    • TCP需要通过seq序列号来做可靠重传或者接收 而避免连接复用时无法分辨出seq是延迟/旧连接的seq
    • 两次握手只能保证一方的初始序列号(也就是SQN报文中的序列号)被对方成功接收
      • 注意!只有一来一回(一方发送SYN 一方发送ACK应答报文确定SYN报文已经被成功接收了!)才算是“成功”
    • 而三次握手(其实是四次 中间的SYN+ACK 可以算作一步)可以保证双方的初始序列号都能被对方接收 这样就不会出现历史连接捣乱的情况!

部分参考资料如下

极客时间中的一个评论

应用层的包 ->(通过socket编程实现包的传递->) 传输层 支付这种场景 往往使用传输层中的TCP协议 TCP会保证这个包能够到达目的地 如果不能到达就会重新发送 直至到达

这个过程就像是一份信你写好了,装进信封,根据收信人的名字(网址)从地址簿(DNS)里面查到了目的地的地址邮编(IP),然后交给邮局(传输层)传输,当然这个过程可能还有些波折。

1.你在信里面说: 亲爱的某某,我们以后写信联系吧(SYN=1发起一个新的连接),我有不能说的秘密跟你讲,但是我不确定收到信的是不是你,如果是你自己就请给我回信,我就跟你分享我的秘密(seq=x)。

2.收信人收到你的信后给你回信: 亲爱的某某某我也想跟你写信(SYN=1发起一个新的连接),我是我自己你可以接着给我写信了(ACK=1确认序列号有效)你可以从不能说的秘密开始接着往下讲(ack=x+1),另外我也有话对你讲(seq=y)

3.你收到信后很高兴,立刻回信: 我们终于联系上了(ACK=1确认序列号有效),我们接着说我的秘密......(seq=x+1),你有什么话跟我说下次写信告诉我(seq=y+1) 这就是TCP三次握手的过程

知乎中的解答

要注意 TCP的可靠连接是靠**seq(sequence numbers 序列号)**来达成的!——来自知乎

  • 因为没有网络全局时钟,两台机器分别为了确认序列号,证明这个包是新的,而不是在链路中delay的(也是小林中内容的第二天点)——来自知乎

谢希仁 版 计算机网络——

“已失效的连接请求报文段” 的产生在这样一种情况下:client 发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达 server。本来这是一个早已失效的报文段。但 server 收到此失效的连接请求报文段后,就误认为是 client 再次发出的一个新的连接请求。于是就向 client 发出确认报文段,同意建立连接。假设不采用 “三次握手”,那么只要 server 发出确认,新的连接就建立了。由于现在 client 并没有发出建立连接的请求,因此不会理睬 server 的确认,也不会向 server 发送数据。但 server 却以为新的运输连接已经建立,并一直等待 client 发来数据。这样,server 的很多资源就白白浪费掉了。采用 “三次握手” 的办法可以防止上述现象发生。例如刚才那种情况,client 不会向 server 的确认发出确认。server 由于收不到确认,就知道 client 并没有要求建立连接。”

具体意思就是——为了防止已经失效的连接请求报文段突然又传到了服务端 因而产生错误

而知乎博主 一名阿里工程师认为这个“只能算是表因,并不涉及本质。“ 所以提出了——

如果你细读RFC793,也就是 TCP 的协议 RFC,你就会发现里面就讲到了为什么三次握手是必须的——

TCP 需要 seq 序列号来做可靠重传或接收,而避免连接复用时无法分辨出 seq 是延迟或者是旧链接的 seq,因此需要三次握手来约定确定双方的 ISN(初始 seq 序列号)。

作者:一位阿里工程师 链接:www.zhihu.com/question/24…