0%

为什么TCP建立个连接得握三次手

互联网发展至今日这个样子,TCP 协议脱不了这个“干系”。TCP 协议已然成为我们日常网络生活中必定会接触到的网络协议,因为绝大多数的网络连接都是建立在其之上。而学习过计算机网络基础或对 TCP 协议稍有了解的人应该都知道,TCP 协议在其连接建立之前需要经过三次握手(three-way handshake)。

而对于 TCP 协议三次握手的建立过程,我相信很多 IT 人在准备面试时都一定会先再熟悉一番。但是一般都很少会去深究其为什么需要握三次手?我相信很多人应该都没有办法回答这个问题。所以今天在这里就让我们稍微深究一下其原因,而在深究前,我想先抛出一个对于三次握手错误的类比,这个类比错误的误导了很多人,也包括我在内。而这个错误的类比如下:

  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.

RFC793 - Transmission Control Protocol 文档中非常清楚的定义了 TCP 协议中的连接是什么东西,在这里简单的总结一下:为了保证传输可靠性和流的控制机制,TCP 协议需要为每个数据段初始化和维护其状态信息,而这些状态信息由:sockets、序列号(sequence no: SEQ)和窗口大小(window size)组成,然后这些数据段发送与接收就构成了 TCP 协议连接。

构成TCP连接的根本要素

所以,建立 TCP 连接其根本就是要让通信双方对于以上三个要素达成共识。而连接中的 sockets 是由互联网地址和端口号组成,即:IP + 端口。窗口大小则主要用来做流控制,最后的序列号则是用来确定连接发起方发送的数据段序列号,接收方可以通过序列号与发送方确认包的成功接收。那么既然这样,问题好像就被转换成了:为什么需要三次握手才能初始化 sockets、序列号和窗口大小? 那么我们就继续顺着网线,分析并查找答案!

历史连接

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两次通信建立连接造成的混乱

可以想象一个场景,如果通信双方建立连接的通信只有两次,那么发送方一旦发出建立连接请求后,它就没办法撤回这一请求。如果在网络情况复杂或者网络状态差的场景下,发送方在想与接收方建立连接时,连续的发送了多次建立连接的请求。此时假设 TCP 协议建立连接只通信两次,那么接收方就只能选择接受或者拒绝,它并不知道此次所收到的建立连接的请求是否是因为网络堵塞,而已被发送方置为过期的请求。

因为这个原因,TCP 协议选择采用了三次握手并在其数据段头引入了 RST 控制标识,当接收方收到请求时就会将发送方发来的 SEQ + 1 发回给对方,这时由发送方根据接收到的 SEQ + 1 的值来判断当前连接是否是历史连接:

  1. 如果发送方判断所收到的 SEQ + 1 的值属于已经过期的 SEQ,那么当前连接就是历史连接,发送方会直接发送数据段,并将段头中的 RST 控制标识置为 1,要求终止这一次连接
  2. 如果发送方判断 SEQ + 1 的值不属于已经过期的 SEQ,那么发送方就会发送数据段,并将段头中的 ACK 控制标识置为 1,通信双方就会成功建立连接

使用三次握手和 RST 控制标识,将连接的是否建立的最终控制权交给了发送方,这是因为只有发送方有足够的上下文信息判断当前进行建立的连接是否存在错误或过期,这也是 TCP 选择使用三次握手建立连接的主要原因。

初始序列号

而另一个使用三次握手的重要因素就是,通信双方都需要交换对方所用于发送消息的初始序列号。TCP 协议作为一个标称可靠的传输层协议,需要在稳定性不确定的网络环境中解决各种可能产生的问题,而最常见的问题如下:

  1. 数据段被发送方多次发送造成数据重复
  2. 数据段在传输过程中被链路中的设备丢失
  3. 数据段到达接收方后顺序被打乱

为了解决以上的这些可能存在的问题,初始序列号就起到了至关重要的作用。而 TCP 协议要求发送方在数据段中必须加入序列号字段,有了该序列号,就可以:

  1. 接收方通过序列号对重复的数据段进行去重处理
  2. 发送方会在对应数据段未被 ACK 的情况下进行重发
  3. 接收方可以根据数据段内的序列号进行数据段的重新排序

所以说序列号在 TCP 协议连接起至关重要的作用并不是空谈,而该初始序列号作为 TCP 协议数据段内的一部分,也就需要发送方在三次握手的第一阶段发送带有 SYN 控制标识数据段时一同在 SEQ 字段赋上初始序列号的值,而接收方则在接收到 SYN 控件标识的数据段后,需要回复带有 ACK 控制标识和值为 SEQ + 1 的确认序列号数据段与之进行确认。

TCP四次通信建立连接

如上图所示,通信双方都分别向对方发送包含 SYN 和 ACK 控制标识的数据段,使对方获取了自己所设置的初始序列号之后连接就建立了。由于 TCP 协议数据段头中的巧妙设计,以至于一个数据段头内可同时包含 ACK 和 SYN 控制标识,所以接收方就可以把中间的两次通信合二为一,这也就帮助我们将通讯次数减少为三次。

TCP三次通信建立连接

除此之外,由于网络作为一个分布式系统,其中并不存在一个用于计数的全局计数器,并且 TCP 可能具有不同的机制来选择初始序列号。接收方在接收第一个包含 SYN 控制标识的数据段时无法知道该数据段是否是因为网络阻塞而刚到达的旧延迟数据段,除非接收方记住了连接上使用的最后一个序列号。这样可行性会比较低,因为一旦这样接收方要记的东西就太多了,但是让发送方记录下发出的序列号来判断连接是否过期就可行的多。

通信次数

当我们讨论 TCP 建立连接需要的通信的次数时,我们经常会执着于为什么通信三次才可以建立连接,而不是两次、四次或者更多次?讨论使用更多的次数来建立连接往往是没有建设性意义的。因为我们总是可以使用更多的通信次数交换相同的东西。而我们追求的其实是用最少的通信次数完成预期的信息交换。也就是我们在上面所说的两次握手没办法建立 TCP 连接,而四次握手又有点多余,所以通过精心设计的数据段头(同时包含 SYN 和 ACK 控制标识)以至于最终可以达到三次握手就能建立 TCP 连接。

总结

文章开始我们把关注点从为什么要三次握手才能建立 TCP 连接上,转向了什么是 TCP 连接,从而使建立 TCP 连接的本质浮出水面。而 TCP 连接有着非常清楚的定义:为了保证传输可靠性和流的控制机制,TCP 协议需要为每个数据段初始化和维护其状态信息,而这些状态信息由:sockets、序列号(sequence no: SEQ)和窗口大小(window size)组成,然后这些数据段发送与接收就构成了 TCP 协议连接。

TCP 建立连接时通过三次握手可以有效的避免历史连接的错误建立,减少通信双方产生不必要的资源消耗,三次握手能帮助通信双方获取初始序列号,有了初始序列号的存在能够保证数据段在传输的过程中不重不乱不丢。到这里我想为什么不使用两次握手四次握手的原因已经非常明了了。

  • 两次握手:无法避免历史连接的错误建立,导致资源浪费
  • 四次握手:TCP 协议数据段头中的精心设计,让一个数据段可以同时存在 SYN 和 ACK 控制标识,以至于可以把接收方的两次发送合二为一,减少了通信次数带来的消耗