【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?

转自连接地址
TCP协议是我们几乎每天都会接触到的网络协议,绝大多数网络协议的协议都是基于TCP协议的,学过计算机网络或者对TCP协议稍有了解的人都知道——使用TCP协议简历链接需要经过三次握手(three-way handshake)。
如果让我们简单说说TCP建立连接的过程,相信很多准备过面试的人都会非常了解,但是一旦想要深究「为什么TCP建立连接需要三次握手?」,作者相信大多数人都没有办法回答这个问题或者给出错误的答案,这篇文章就会讨论为什么我们需要三次握手才能建立TCP连接。
需要注意的是我们会将重点放到为什么TCP建立需要「三次握手」,而不仅仅是为什么需要「三次」握手
概述 在具体分析今天的问题之前,我们首先可以了解一下最常见的错误类比,这个对TCP链接过程的错误比喻误导了很多人,作者在比较长的一段时间内也认为它能够很好地描述TCP建立连接为什么需要三次握手:
  1. 你能听得到吗?
  2. 我能听到,你能听到?
  3. 我也能听到。
这种类比来解释问题往往就会面临「十个类比九个错」的尴尬局面,如果别人用类比回答你的为什么,你需要仔细想一想他的类比里究竟哪里有漏洞;类比带来的解释往往只能有片面的相似性,我们永远也无法找到决定正确的类比,它只在我们想要通俗易懂地展示事务的特性时才能发挥较大的作用,我们在文章的后面会介绍为什么这里的类比有问题,各位读者也可以带着疑问来阅读剩下的内容。
很多人尝试回答或者思考这个问题的时候其实关注点都放在三次握手中的三次上面,这确实很重要,但是如果重新审视这个问题,我们对于「什么是连接」真的清楚?只有知道链接的定义,我们才能去尝试回答为什么TCP连接需要三次握手。
The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.
RFC 793 - Transmission Control Protocol 文档中非常清楚地定义了TCP中的连接是什么,我们简单总结一下:用于保证可靠性和流控制机制的信息,包括Socket、序列号以及窗口大小叫做连接。

【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?
文章图片

所以,建立TCP连接就是通信的双方需要对上述的三种信息达成共识,连接中的一对Socket是由互联网地址标志符合端口组成的,窗口大小主要做流控制,最后的序列号是用来追踪通信发起方发送的数据包序号,接收方可以通过序列号向发送方确认某个数据包的成功接收。
到这里,我们将原有的问题转换成了「为什么需要通过三次握手才可以初始化Sockets、窗口大小和初始序列号?」,那么接下来我们就开始对这个细化的问题进行分析并寻找解释。
设计 这篇文章主要会从以下几个方面介绍为什么我们需要通过三次握手才可以初始化Sockets、窗口大小、初始化序列号并建立TCP连接:
  • 通过三次握手才能阻止重复历史连接的初始化;
  • 通过三次握手才能对通信双方的初始序列号进行初始化;
  • 讨论其他次数握手建立的可能性;
这几个论点中的第一个是TCP选择使用三次握手的最主要原因,其他的几个原因相比之下都是次要的原因,我们在这里对他们的讨论只是为了让整个视角更加丰富,通过多方面理解这一有趣的设计决策
历史连接 RFC 793 - Transmission Control Protocol 其实就指出了TCP连接使用三次握手的首要原因——为了阻止历史的重复连接初始化造成的混乱问题,防止使用TCP协议通信的双方建立了错误的连接。
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?
文章图片
想象一下这个场景,如果通信双方的通信次数只有两次,那么发送方一旦发出建立连接的请求之后它就没有办法撤回这一次请求,如果在网络状况复杂或者较差的网络中,发送方连续发送多次建立连接的请求,如果TCP建立连接只能通信两次,那么接收方只能选择接受或者拒绝发送方发起的请求,它并不清楚这一次请求是不是由于网络拥堵而早早过期的连接。
所以,TCP选择三次握手来建立连接并在连接引入了RST这一控制消息,接收方当收到请求时会将发送方发来的SEQ+1发送回接收方,这时由发送发来判断当前连接是否是历史连接:
  • 如果当前连接是历史连接,即SEQ过期或者超时,那么发送方会直接发送RST控制消息中止这一次连接;
  • 如果是当前连接不是历史连接,那么发送方就会发送ACK控制消息,通信双方就会成功建立连接;
使用三次握手和RST控制消息将是否建立连接的最终控制权交给了发送方,因为只有发送方有足够的上下文来判断当前连接是否是错误的或者过期的,这也是TCP使用三次握手建立连接的最主要原因。
初始序列号 另一个使用三次握手的重要的原因就是通信双方都需要获得一个用于发送信息的初始化序列号,作为一个可靠的传输层协议,TCP需要在不稳定的网络环境中构建一个可靠的传输层,网络的不确定性可能会导致数据包的缺失和顺序颠倒等问题,常见的问题可能包括:
  • 数据包被发送方多次发送造成数据的重复;
  • 数据包在传输的过程中被路由或者其他节点丢失;
  • 数据包达到接收方可能无法按照发送顺序;
为了解决上述这些可能存在的问题,TCP协议要求发送方在数据包中加入「序列号」字段,有了数据包对应的序列号,我们就可以:
  • 接收方可以通过序列号对重复的数据包进行去重;
  • 发送方会在对应数据包未被ACK时进行重复发送;
  • 接收方可以根据数据包的序列号对它们进行重新排序;
序列号在TCP连接中有着非常重要的作用,初始序列号作为TCP连接的一部分也需要在三次我收期间进行初始化,由于TCP连接通信的双方都需要获得初始序列号,所以它们其实需要向双方发送SYN控制消息并携带自己期望的初始化序列号SEQ,对方在接受到SYN消息之后会通过ACK控制消息以及SEQ+1来进行确认。

【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?
文章图片

如上图所示,通信双方的两个 TCP A/B分别向对方发送了 SYNACK控制消息,等待通信双方都获取到了自己期望的初始化序列号之后就开始通信了,由于TCP消息头的设计,我们可以将中间的两次通信合成一个, TCP B可以向 TCP A同时发送 ACKSYN控制消息,这也就帮助我们将四次通信减少至三次。
A three way handshake is necessary because sequence numbers are not tied to a global clock in the network, and TCPs may have different mechanisms for picking the ISN's. The receiver of the first SYN has no way of knowing whether the segment was and old delayed one or not, unless it remembers the last sequence number used on the connection (which is not always possible), and so it must ask the sender to verify this SYN. The three way handshake and the advantages of a clock-driven scheme are discussed in [3].
除此之外,网络作为一个分布式系统,其中并不存在一个用于计数的全局时钟,而TCP可以通过不同的机制来初始化序列号,作为TCP连接的接收方我们无法判断对方传来的初始化序列号是否过期,所以我们需要交由对方来判断,TCP连接的发起方可以通过保存发出的序列号来判断连接是否过期,如果让接收方来保存并判断序列号却是不现实的,这也再一次强化了我们在上一节提出的观点——避免历史错连接的初始化。
通信次数 当我们讨论TCP建立连接需要的通信次数时,我们经常执着于为什么通信三次才可以建立连接,而不是两次或者四次;讨论使用更多的通信次数来建立连接往往是没有意义的,因为我们总是可以使用更多的通信次数交换相同的信息,所以使用四次、五次或者更多次建立连接在技术上都是完全可以实现的。

【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?
文章图片

这种增加TCP连接通信次数的问题往往讨论的必要性,我们追求的其实是用更少的通信次数(理论上的边界)完成信息交换,也就是为什么我们在上两节中一再强调使用「两次握手」没有办法建立TCP连接,使用三次握手是建立连接所需的最小次数。
总结 我们在这篇文章中讨论了为什么TCP建立连接需要经过三次握手,在具体分析这个问题之前,我们首先重新思考了TCP连接究竟是什么,RFC 793 - Transmission Control Protocol - IETF Tools 对TCP链接有着非常清楚的定义——用于保证可靠性和流控制机制的数据,包括Socket、序列号和窗口大小。
TCP建立连接时通过三次握手可以有效地避免历史错误连接信息的建立,减少通信双方不必要的资源消耗,三次握手能够帮助通信双方获取初始化序列号,它们能够保证数据包传输的不重不丢,还能保证它们的传输顺序,不会因为网络传输的问题发生混乱,到不使用「两次握手」和「四次握手」的原因已经非常清楚了:
  • 「两次握手」:无法避免历史错误连接的初始化,浪费接收方的资源;
  • 「四次握手」:TCP协议的设计可以让我们同时传递ACKSYN两个控制信息,减少了通信次数,所以不需要使用更多的通信次数传输相同的信息;
【【转载】为什么|【转载】为什么 TCP 建立连接需要三次握手 · Why's THE Design?】我们重新回到在文章开头提的问题,为什么使用类比解释TCP使用三次握手是错误的?这主要还是因为,这个类比没有解释清楚核心问题——避免历史上的重复连接。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细想一下下面的问题:
  • 除了使用序列号是否还有其他方式保证消息的不重不丢?
  • UDP协议有链接的概念么,它能保证数据传输的可靠性么?

    推荐阅读