TCP是如何进行连接的

1,246 阅读15分钟

在前面的文章中我们已经学习了可靠数据传输的基本原理,我们就可以转而学习 TCP 了,它 是因特网传输成的面向链接的可靠的传输协议。

TCP 连接

TCP 被称为是面向连接的,这是因为在一个应用进程可以开始向另一个应用进程发送数据之前,这两个进程必须先相互 握手,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。

TCP 连接提供的是全双工服务,如果一台主机上的进程 A 与另一台主机上的进程 B 存在一条 TCP 连接,那么应用层数据就可以从进程 B 流向进程 A的同时,也从进程 A 流向进程 B,TCP 的链接也中是点对点的,即在单个发送方与单个接收方之间的连接。

TCP 会在数据通信之前,通过 TCP 首部发送一个 SYN 包作为建立连接的请求等待确认应答,如果对端发来确认应答,则认为可以进行数据通信,如果对端的确认应答未能到达,就不会进行数据通信。此外,在通信结束时会进行断开连接的处理,即服务端向客户端发送一个 FIN 包。

image.png

TCP以端为单位发送数据

在建立 TCP 连接的同时,也可以确定发送数据报的单位,我们也可以称其为 最大消息长度(MSS) 最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。

TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送,进行重发也是以 MSS 为单位。

MSS 是在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出简历连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自动的接口能够适应的 MSS 的大小,然后会在两者之间选择一个较小的值投入使用。

image.png

为附加 MSS 选项,TCP 首部将不再是 20 字节,而是 4 字节的整数倍。

TCP报文段结构

在上面的内容中讲到 MSS 限制了报文段数据字段的最大长度,当 TCP 发送一个大文件,例如某 Web 页面上的一个图像时,TCP 通常是将该文件划分为长度为 MSS 的若干块。

image.png

上图展示了 TCP 首部的格式,TCP 首部相比 UDP 首部要复杂得多。另外TCP 中没有表示包长度和数据长度的字段,可由 IP 层获知 TCP 的包长由 TCP 的包长可知数据的长度。

我们接下来看这些首部字段都包含了什么意义:

  • 源端口号: 表示发送端的端口号,字段长 16 位;
  • 目标端口号: 表示接收端端口号,字段长 16 位;
  • 序列号: 字段长 32 位,序列号有时也叫序号,是指发送数据的位置,每发送一次数据,就累加一次该数据字节数的大小。序列号不会从 0 或者 1 开始,而是在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,然后将每转发过去的字节数累加到初始值上表示数据的位置。此外,在建立连接和断开连接时发送的 SYN 包和 FIN 包虽然不携带数据,但是也会作为一个字节增加对应的序列号;
  • 确认应答号: 确认应答号字段长度 32 位,是指下一次应该接收到的数据的序列号,实际上是指已收到确认应答号前一位位置的数据。发送端收到这个确认应答以后可以认为在这个序号一起的数据都被已经被正常接收;
  • 数据偏移: 该最短表示 TCP 所传输的数据部分应该从 TCP 包在哪个位置开始计算,当然也可以把它看作 TCP 首部的长度。该字段长 4 位,单位为 4 字节,不包含选项字段的话,如上图所示,TCP 的首部为 20 字长,因此数据偏移字段可以设置为 5,如果该字段的值为 5,那说明从 TCP 包的最一开始到 20 字节位置都是 TCP 首部,余下的部分为 TCP 数据;
  • 保留: 该字段主要是为了以后扩展时使用,其长度为 4 位,一般设置为 0,但即使收到的包在该字段不为 0,此包也不会被丢弃;
  • 控制位: 字段长为 8 位,每一位从左至右分别为 CWRECEURGACKPSHRSTSYNFIN。这些控制标志也叫做控制位,如下图所示:

image.png

  • 窗口大小:该字段长为 16 位,用于通知从相用 TCP 首部的确认应答号所指位置开始能够接收的数据大小为 8 字节,TCP 不允许发送超过此处所示大小的数据,如果窗口为 0,则表示可以发送窗口探测,以了解最新的窗口大小,但这个数据必须是 1 字节;
  • 校验和: TCP 的校验和与 UDP 相似,区别在于 TCP 的校验和无法关闭。TCPUDP 一样在计算校验和的时候使用 TCP 伪首部,这个伪首部如下图所示,为了让其全长为 16 为的整数倍,需要在数据部分里的最后填充 0。首先将 TCP 校验和字段设置为 0,然后以 16 位为单位进行 1 的补码和计算,再将它们总和的 1 的补码和放入校验和字段。接收端在收到 TCP 数据段以后,从 IP 首部获取 IP 地址信息构造 TCP 伪首部,再进行校验和计算。由于校验和字段里保存着除本字段意外其他部分的和的补码值,因此如果计算校验和字段在内的所有数据的 16 位和以后,得出的结果是 16 位全部为 1,说明所收到的数据是正确的;

image.png

可靠数据传输

在前面的文章中讲过,因特网的网络层服务即 IP 服务是不可靠的,IP 不保证数据报的交付,不保证数据报的按序交付,也不保证数据报中的完整性,对于 IP 服务,数据报能够溢出路由器缓存而永远不能到达目的地,数据报也可能乱序到达,而且数据报中的比特可能损坏,从 0 变为 1 或者相反。由于传输层的报文段是被 IP 数据报携带着在网络中传输的,所以传输层的报文段也会遇到这些问题。

TCPIP 不可靠的尽力而为服务智商创建了一种可靠数据传输服务。TCP 的可靠数据传输服务确保一个进程从其接收缓存中的读出的数据流是无损坏、无间隙、非冗余和按序的数据流,即该字节流和连接的另一方端系统发送出的字节流是完全相同。

超时间隔加倍

TCO 重传具有最小序号的还未被确认的报文段,知识每次 TCP 重传时都会将一下次的超时间隔设为先前的两倍。

因此,超时间隔在每次重传后会呈指数型增长。

快速重传

超时触发重传存在的问题之一是超时周期可能相对较长,当一个报文段丢失时,这种长超时周期迫使发送发延迟重传丢失的分组,因而增加了端到端时延。

幸运的是,发送发通常可在超时事件发生之前通过注意所谓 ACK 冗余来较好地检测到丢包情况。冗余ACK 就是再次确认某个报文段的 ACK,而发送方先前已经收到对该报文段的确认。

因为发送方经常一个接一个地发送大量的报文段,如果一个报文段丢失,就很可能引起许多一个接一个的冗余 ACK,如果 TCP 发送方接收到对相同数据的 3 个冗余 ACK,它把这当做一种指示,说明跟在这个已确认过三次的报文段之后的报文段已经丢失。

是回退 N 步 还是选择重传

TCP 确认是累积式的,正确接收但时序的报文段是不会被接收方确认的,因此 TCP 发送方仅需维持已发送过但未被去人的字节的最小序号和下一个要发送的字节的序号。

在这种意义下,TCP 看起来更像一个 GBN 风格的协议,但是 TCPGBN 协议之间有着一些显著的区别,许多 TCP 实现会将正确接收但失序的报文段缓存起来。

TCP 提出的一种修改意见是所谓的 选择确认,它运行 TCP 接收方 有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序报文段。

当将该机制与选择重传机制结合起来使用时,那些被被收方选择性确认过的报文段会被跳过,TCP 看起来就很像我们通常的 SR 协议。因此,TCP 的差错恢复机制也许最好被分类为 GBN 协议与 SR 协议的混合体。

流量控制

一条 TCP 连接的每一册主机都为该连接设置了接收缓存,当该 TCP 连接正确、按序的字节后,它就将数据放入接收缓存,相关联的应用程序会从缓存中读取数据,但不必是数据刚一到达就立即读取。

事实上,接收方应用也许正忙于其他任务,甚至要过很长时间后才读取该数据,如果某应用程序读取数据时相对缓慢,而发送方发送得太多、太快,发送的数据就会很容易地使该连接的接收缓存溢出。

TCP 为它的应用程序提供了 流量控制服务 以消除发送方使接收方缓存溢出的可能性,流量控制因此是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配。

TCP 通过让发送方维护一个称为接收窗口的变量来提供流量控制,通俗地说,接收窗口用于发送发一个指示---该接收方还有多少可用的缓存空间。因为 TCP 是全双工通信,在连接两端的发送方都各自维护一个接收窗口。

TCP 首部中,专门有一个字段用来通知窗口大小,接收主机将自己可以接收的缓冲区大小放入这个字段中通知给发送端,这个字段的值越大,说明网络的吞吐量越高。发送端主机会根据接收端主机的指示,对发送数据的量进行控制,这也就形成了一个完整的流量控制,如下图所示: image.png

在上图中,当接收端收到从 3001 号开始的数据段后其缓存区即满,不得不暂时停止接收数据,之后,在收到发送窗口更新通知后通信才得以继续进行。如果这个窗口的更新通知在传送途中丢失,可能会导致无法继续通信,为避免此类问题的发生,发送端主机会时不时发送一个叫作窗口探测的数据段,此数据段仅含一个字节以获取最新的窗口大小信息。

TCP 连接管理

TCP 提供面向有连接的通信传输,面向有连接是指在数据通信开始之前先做好通信两端之间的准备工作。

在上面的内容中我们已经讲到了 TCP 连接三次握手和四次挥手了,这篇我们就来详细讲讲这个内容,假设运行在一台客户机上的进程想与服务器上的一个进程简历一条连接,客户应用进程首先通知客户 TCP,它像建立一个与服务器上某个进程之间的连接,客户端中的 TCP 会用以下方式与中服务器的 TCP 建立一条 TCP 连接:

  • 第一步: 客户端的 TCP 首先向服务器端的 TCP 发送一个特殊的 TCP 报文段。该报文段中不包含应用层数据。但是在报文段的首部中的一个标准位即 SYN 被设置为 1。因此这个特殊报文段被称为 SYN 报文段。另外客户端随机地选择一个初始序号 client_isn,并将此编号放置于该起始的 TCP SYN 报文段的序号字段中,该字段会被封装在一个 IP 数据报中并发送给服务器中;
  • 第二步: 一旦包含 TCP SYN 报文段中的 IP 数据报到达服务器主机,服务器会从该数据中提取出 SYN 报文段,为该 TCP 连接分配 TCP 缓存和变量,并向该客户 TCP 发送运行连接的报文段。这个连接允许的报文段不包含应用层数据,但是在报文段的首部却包含了三个重要的信息。首先 SYN 被设置为 1,其次 TCP 包围的那段首部的确认号字段被设置为 client_isn + 1。最后服务器选择自己的初始序号并将其放置到 TCP 报文段首部的序号字段中,该报文段被称之为 SYNACK 报文段;
  • 第三步: 在收到 SYNACK 报文段后,客户也要给该连接分配缓存和变量。客户主机则向该服务器发送另外一个报文段段。这最后一个报文段对服务器的运行连接的报文段进行了确认。因为连接已经简历了,所以该 SYN 比特被设置为 0,该三次握手的第三个阶段可以在报文段负载中写道客户到服务器的数据;

第三次握手的目的是为了确保双方都能够正常地发送和接收数据,并使双方的初始序列号和确认号能够正确匹配,以建立一个可靠的通信连接。通过三次握手,客户端和服务端都确认了彼此的能力和意愿,确保后续的数据传输能够可靠地进行。

一旦完成这三个步骤,客户和服务器主机就可以相互发送包括数据的报文段了,在以后的每一个报文段中,SYN 比特都将被设置为 0:

image.png

天下没有不散的筵席,对于 TCP 连接也是如此,参与 TCP 连接的两个进程中的任何一个都能终止该连接,当连接结束后,主机中的缓存和变量都将被释放。

当客户端和服务器通过 TCP 连接进行通信时,断开连接需要进行四次挥手,,以下是 TCP 四次挥手的详细步骤:

  1. 第一次挥手: 客户端发送一个 FIN(Finish) 标志给服务器,表示客户端不再发送数据。此时客户端进入 FIN_WAIT_1 状态;
  2. 第二次挥手: 服务器接收到 FIN 标志之后,向客户端发送一个 ACK 确认标志,表示已经收到了客户端发送的 FIN。同时,服务器自己也发送一个 FIN 给客户端,此时服务器进入 COLSE_WAIT 状态,客户端进入 FIN_WAIT_2状态;
  3. 第三次挥手: 客户端收到服务器发送的 FIN 后,向服务器发送一个 ACK 确认标志,表示已经收到了服务器发送的 FIN,此时客户端进入 TIME_WAIT 状态,等待可能迷路的 ACK消息;
  4. 第四次挥手: 服务器收到客户端发送的 ACK 后,确认客户端已经收到了服务器发送的 FIN,并且完成了连接的关闭。服务器进入 CLOSED 状态,连接完全关闭;

四次挥手的过程保证了双方都能安全地关闭连接,这是因为 TCP 是全双工通信协议,每个方向上的关闭都需要单独进行处理。客户端和服务器都必须发送自己的 FIN 来关闭连接,并接收对方的 ACK 确认。最后,等待一段时间后才能彻底关闭连接,以确保最后一个ACK的可靠性。

总结

TCP 之所以需要 3 次握手,是因为 TCP 通讯双方都是全双工的,所以要经过 3 次交互才能确认双方的发送能力和接收能力,并且 TCP 握手必须是 3 次,如果是 2 次握手,不能证明服务器端的发送能力和客户端的接收能力;也不能是 4次握手;因为 3 次已经能证明的事情,再交互握手 1 次完全没有必要。

参考文献

  • 书籍: 计算机网络自顶向下方法原书第七版;
  • 书籍: 图解 TCP;