实际上在计算机网络中进行通信的真正实体是位于通信两端主机中的进程。如何为运行在不同主机上的应用进程提供直接的通信服务是运输层的任务,运输层协议又称为端到端协议。

(1)运输层为相互通信的应用进程提供逻辑通信。

(2)运输层的复用与端口的概念。

(3)无连接的UDP的特点。

(4)面向连接的TCP实现可靠传输的工作原理,以及TCP的滑动窗口、流量控制、拥塞控制和连接管理。

运输层协议概述

进程之间的通信

只有主机的协议栈才有运输层,而网络核心部分中的路由器在转发分组时都只用到下三层。下面的示意图说明运输层的作用。

运输层的一个很重要的功能就是复用(multiplexing)和分用(demultiplexing)。

这里的“复用”是指在发送方不同的应用进程都可以使用同一个运输层协议传送数据(当然需要加上适当的首部),而“分用”是指接收方的运输层在剥去报文的首部后能够把这些数据正确交付到目的应用进程。

image-20210805164751433

网络层是为主机之间提供逻辑通信,而运输层为应用进程之间提供端到端的逻辑通信(见下图)。

image-20210805164830781

运输层协议还可以在网络层协议之上实现许多其他重要功能,如可靠数据传输、流量控制、拥塞控制等。根据应用需求的不同,因特网的运输层为应用层提供了两种不同的运输协议,即面向连接的TCP和无连接的UDP

因特网的运输层协议

单一的运输层服务很难满足所有应用的需求。因特网(更一般地说是TCP/IP网络)为上层应用提供了两个不同的运输层协议:

(1)用户数据报协议(User Datagram Protocol,UDP)[RFC 768];

(2)传输控制协议(Transmission Control Protocol,TCP)[RFC 793]。

报文在因特网中,称之为TCP报文段(segment)和UDP报文或用户数据报。

UDP在传送数据之前不需要先建立连接。远地主机的运输层在收到UDP报文后,不需要给出任何确认。

TCP则提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。TCP不提供广播或多播服务。由于TCP要提供可靠的、面向连接的运输服务,因此不可避免地增加了许多开销,如确认、流量控制、计时器及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多的处理机资源。

下表给出了一些应用和应用层协议主要使用的运输层协议(UDP或TCP)。

image-20210805165621770

运输层的复用与分用

应用层所有的应用进程都可以通过运输层再传送到IP层,这就是复用。

运输层从IP层收到数据后必须交付给指明的应用进程。这就是分用。

在 TCP/IP 网络中,使用一种与操作系统无关的协议端口号(简称端口号)来实现对通信的应用进程的标志。端口号只具有本地意义。在因特网不同计算机中,相同的端口号是没有联系的,并且TCP和UDP端口号之间也没有必然联系。16位的端口号可以允许有65535个端口号。

在应用层中的各种不同的服务器进程不断地监听它们的端口,以便发现是否有某个客户进程要和它通信。客户在发起通信请求时,必须先知道对方服务器的IP地址和端口号,而服务器总是可以从接收到的报文中获得客户的IP地址和端口号。

运输层的端口号分3类:

  1. 熟知端口(well-known port),其数值为0~1023。
  2. 登记端口,其数值为1024~49151。这类端口IANA不分配也不控制,但可以在IANA注册登记,以防止重复使用。
  3. 动态端口,其数值为 49152~65535。

户数据报协议 UDP

概述

UDP只在IP的数据报服务之上增加了端口和差错检测的功能。UDP在某些方面有其特殊的优点:

  1. UDP是无连接的,即发送数据之前不需要建立连接,减少了开销和发送数据之前的时延。
  2. UDP不保证可靠交付,同时也不使用流量控制和拥塞控制,主机不需要维持具有许多参数的、复杂的连接状态表。
  3. 网络出现的拥塞不会使源主机的发送速率降低。实时应用(如IP电话、实时视频会议等)要求源主机以恒定的速率发送数据,并且允许丢失一些数据,但却不允许数据有太大的时延。
  4. UDP对应用程序交下来的报文不再划分为若干个分组,也不把收到的若干个报文合并后再交付给应用程序。应用程序交给 UDP 一个报文,UDP就发送这个报文;而UDP收到一个报文,就把它交付给应用程序。若报文太长,UDP把它交给IP层后,IP层可能要进行分片,这会降低IP层的效率。若报文太短,会使IP数据报的首部相对太大,降低了IP层的效率。
  5. UDP支持一对一、一对多、多对一和多对多的交互通信。
  6. 用户数据报只有8个字节的首部开销,比TCP的20个字节的首部要短得多。

很多源主机同时都向网络发送UDP报文时,有可能发生严重拥塞。

UDP 的首部格式

UDP报文有两个字段:数据字段和首部字段。首部字段很简单,只有8个字节(见下图):

  1. 源端口号。
  2. 目的端口号。
  3. UDP 用户数据报的长度。
  4. 差错检验码,防止 UDP 用户数据报在传输中出错。

image-20210806125308381

UDP的多路分用模型如下图。一个UDP端口与一个报文队列(缓存)关联,UDP根据目的端口号将到达的报文加到对应的队列。应用进程根据需要从端口对应的队列中读取整个报文。由于UDP没有流量控制功能,如果报文到达的速度长期大于应用进程从队列中读取报文的速度,则会导致队列溢出和报文丢失。

与TCP不同,端口队列中的所有报文的目的IP地址和目的端口号相同,但源IP地址和源端口号并不一定相同。即不同源而同一目的地的报文会定位到同一队列;因为UDP是非连接的,二元组(目的IP,目的端口)确定一个缓存队列。TCP是四元组。

image-20210806125351866

如果接收方 UDP 发现收到的报文中的目的端口号不正确(没有对应进程),就丢弃该报文,并由网际控制报文协议ICMP发送一个“端口不可达”差错报文给发送方。traceroute就是让发送的UDP用户数据报故意使用一个非法的UDP端口,结果ICMP就返回“端口不可达”差错报文,因而达到了测试的目的。

UDP计算检验和时,要在UDP用户数据报之前增加12个字节的伪首部。伪首部既不向下传送也不向上递交,而仅仅是为了计算检验和,防止报文被意外地交付到错误的目的地。

IP数据报的检验和只检验IP数据报的首部,但UDP的检验和是把首部和数据部分一起都检验。伪首部的第3字段是全零。第4个字段是IP首部中的协议字段的值,对于UDP为17。第5字段是UDP用户数据报的长度。这样的检验和,既检查了UDP用户数据报的源端口号、目的端口号及UDP用户数据报的数据部分,又检查了IP数据报的源IP地址和目的地址。

传输控制协议TCP

TCP是TCP/IP体系中面向连接的运输层协议,它提供全双工的和可靠交付的服务。TCP是面向连接的,而UDP是无连接的。TCP比UDP要复杂得多,除了具有面向连接和可靠传输的特性外,TCP还在运输层使用了流量控制和拥塞控制机制。

TCP的主要特点

特点:

(1)TCP是面向连接的运输层协议。应用程序必须先建立TCP连接,初始化各种状态变量,分配缓存等资源。在传送数据完毕后,必须释放已建立的TCP连接,即释放相应的资源和变量。

(2)每一条TCP连接只能有两端点(一对一)。TCP连接唯一地被通信两端的端点所确定,而两个端点分别由二元组(IP地址、端口号)唯一标识,即一条TCP连接由两个套接字(socket)地址标识。

(3)TCP提供可靠交付的服务。通过TCP连接传送的数据无差错、不丢失、不重复,并且按序到达。

(4)TCP提供全双工通信。TCP连接的两端都设有发送缓存和接收缓存。发送时应用程序在把数据传送给TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。接收时把收到的数据放入缓存,应用进程在合适时读取缓存中的数据。

(5)面向字节流。TCP中的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成是一连串的无结构的字节流。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系(例如,发送方应用程序交给发送方的TCP共10个数据块,而接收方的应用程序是分4次(即4个数据块)从TCP接收方缓存中将数据读取完毕。)但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。

下面是上诉概率的示意图:

image-20210806133239068

发送方的应用进程按照自己产生数据的规律,不断地把数据块(其长短可能各异)陆续写入到TCP的发送缓存中。TCP再从发送缓存中取出一定数量的数据,将其组成TCP报文段(segment)逐个传送给IP层。

图中表示的是在TCP连接上传送一个个TCP报文段,而没有画出IP层或链路层的动作。接收方从IP层收到TCP报文段后,先把它暂存在接收缓存中,然后等待接收方的应用进程从接收缓存中将数据按顺序读取。

注意接收方应用进程每次从接收缓存中读取数据时,是按应用进程指定的数量读取数据,而不是一次读取接收缓存中的一个完整的报文段或所有数据。只有当接收缓存中的数据量小于应用进程指定的读取量时,才返回给应用进程接收缓冲中所有的数据。

当接收缓存中完全没有数据时,根据读取方式的不同,应用进程可能会一直等待,也可能直接返回。由此可见,TCP的接收方应用进程读取的数据块的边界与发送方应用进程发送的数据块边界毫无关系。

TCP报文段首先要传送到IP层,加上IP首部后,到数据链路层再加上数据链路层的首部和尾部,才离开主机发送到物理链路。另外,TCP连接仅存在于两个端系统中,而网络核心的中间设备(路由器、交换机等)完全不知道该连接的存在。TCP连接的组成主要包括:通信两端主机上的缓存、状态变量,在这两台主机间的路由器和交换机没有为该连接分配任何缓存和变量。

与 UDP 的端口队列不同的是,TCP 的发送缓存和接收缓存都是分配给一个连接的,而不是一个端口。TCP的一个连接由四元组(源IP地址、源端口号、目的IP地址、目的端口号)标识,即由源/目的套接字(socket)地址对标识。

也就是说,来自不同源的TCP报文段,即使它们的目的IP地址和目的端口号相同,它们也不可能被交付到同一个TCP接收缓存中,因为它们在不同的TCP“管道”中传输,到达不同“管道”出口的缓存。通常一个TCP服务器进程用一个端口号与不同的客户机进程建立多个连接,然后创建多个子进程分别用这些连接与各自的客户机进程进行通信。

TCP报文段结构

TCP报文段分为首部和数据两部分,见下图:

image-20210806142007142

TCP首部的前20个字节是固定的,后面有4N字节是根据需要而增加的选项(N必须是整数)。

(1)源端口和目的端口各占 2 个字节。

(2)序号占 4 字节。序号从 0 开始,到 2的32次方– 1 为止(即 4 294 967 296)。在一个TCP连接中传送的数据流中的每一个字节都按顺序编号。整个数据的起始序号在连接建立时设置。首部中的序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。例如,一报文段的序号字段值是 301,而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。显然,下一个报文段的数据序号应当从401开始,因而下一个报文段的序号字段值应为401。

(3)确认号 占 4 字节,是期望收到对方的下一个报文段的第一个数据字节的序号。TCP 提供的是双向通信,当一端发送数据时同时对接收到的对端数据进行确认。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节,这表明B正确收到了A发送的序号在501~700的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701,表示对第701字节之前(不包括第701字节)的所有字节的确认。TCP采用的是累积确认。序号有 32 位,可对 4 GB 的数据进行编号。可保证在大多数情况下当序号重复使用时,旧序号的数据早已通过网络到达终点。

(4)数据偏移 占 4 位,它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。这实际上就是TCP报文段首部的长度,除非首部有可选字段。但应注意,“数据偏移”的单位不是字节而是32位(4字节)。由于4位二进制数能够表示的最大十进制数字是15,因此数据偏移的最大值是15 X 4 = 60字节(即可选最多40字节),这也是TCP首部的最大长度。

(5)保留 占 6 位,保留为今后使用,但目前应置为 0。

下面有6个标志位说明本报文段的性质,详见(6)~(11)

(6)紧急 URG (URGent) 当 URG=1 时,表明紧急指针字段(2字节)有效。它告诉接收方 TCP此报文段中有紧急数据,应尽快交付给应用程序(相当于高优先级的数据),而不要按序从接收缓存中读取。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行。因此用户从键盘发出中断命令(Control+C)。如果不使用紧急数据,那么这两个字符将存储在接收TCP缓存的末尾。只有在所有的数据被处理完毕后这两个字符才被交付到接收应用进程。这样做就浪费了许多时间。

当URG置1时,发送应用进程就告诉发送TCP这两个字符是紧急数据。于是发送TCP就将这两个字符插入到报文段的数据的最前面,其余的数据都是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。紧急指针指出在本报文段中的紧急数据共有多少个字节。紧急数据到达接收方后,当所有紧急数据都被处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。URG在实际中很少被使用。

(7)确认 ACK 只有当 ACK=1 时确认号字段才有效。当 ACK=0 时,确认号无效。

(8)推送 PSH(PUSH) 出于效率的考虑,TCP 可能会延迟发送数据或向应用程序延迟交付数据。但是当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,应用程序可以通知TCP使用推送(PUSH)操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去,而不需要积累到足够多的数据再发送。接收TCP收到PSH置1的报文段,就尽快地交付给接收应用进程,而不再等到接收到足够多的数据才向上交付。虽然应用程序可以选择推送操作,但现在多数TCP实现都是根据情况自动设置PUSH标志,而不是交由应用程序去处理。

(9)复位 RST(ReSeT) 当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST 置 1 还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。

(10)同步 SYN 用来建立一个连接。当 SYN=1 而 ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1 和 ACK=1。因此,SYN 置为 1 就表示这是一个连接请求或连接接受报文。

(11)终止 FIN(Final) 用来释放一个连接。当 FIN=1 时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。

(12)窗口 占 2 字节。窗口值指示发送该报文段一方的接收窗口大小,在 0 到 2<16– 1 之间。窗口字段用来控制对方发送的数据量(从确认号开始,允许对方发送的数据量),单位为字节。窗口字段反映了接收方接收缓存的可用空间大小,计算机网络经常用接收方的接收能力的大小来控制发送方的数据发送量。 例如,设确认号是701,窗口字段是1000。这表明,允许对方发送数据的序号范围为701~1700。

(13)检验和 占 2 字节。检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报一样,在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。伪首部的格式与前面UDP用户数据报的伪首部一样。但应将伪首部第4个字段中的17改为6(TCP的协议号是6),将第5字段中的UDP长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。

(14)选项 长度可变。这里我们只介绍一种选项,即最大报文段长度(Maximum Segment Size,MSS)。MSS告诉对方TCP:“我的缓存所能接收的报文段的数据字段的最大长度是MSS个字节。”当没有使用选项时,TCP的首部长度是20字节。

MSS的选择并不太简单。若选择较小的MSS长度,网络的利用率就降低。当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部各20字节)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。但反过来,若TCP报文段非常长,那么在IP层传输时就有可能要分解成多个短数据报片。在目的站要将收到的各个短数据报片装配成原来的TCP报文段。当传输出错时还要进行重传。这些也都会使开销增大。一般认为,MSS 应尽可能大些,只要在 IP 层传输时不需要再分片就行。(以太网IPv4的MSS典型值是1460)。在连接建立的过程中,双方可以将自己能够支持的MSS写入这一字段。在以后的数据传送阶段,MSS取双方提出的较小的那个数值。若主机未填写这项,则MSS的默认值是536 字节长。因此,所有在因特网上的主机都应能接受的报文段长度是 536+20=556 字节。

TCP 的可靠传输

有线网中的运输层采用了传输效率更高的基于流水线方式的滑动窗口协议。

1.数据编号与确认

TCP协议是面向字节的。TCP把应用层交下来的长报文(可能要划分为许多短报文)看成是一个个字节组成的数据流,并使每一个字节对应于一个序号。注意,在 GBN 协议(回退N步)中是对每个分组进行编号的。在连接建立时,双方TCP要各自确定初始序号。TCP每次发送的报文段的首部中的序号字段数值表示该报文段中紧接着首部后面的第一个数据字节的序号。

TCP使用的是累积确认,即确认是对所有按序接收到的数据的确认。但请注意,接收方返回的确认号是已按序收到的数据的最高序号加 1。也就是说,确认号表示接收方期望下次收到的数据中的第一个数据字节序号。例如,已经收到了1~700号、801~1000号和1201~1500号,而701~800号及1001~1200号的数据还没有收到,那么这时发送的确认序号应填入701。

当TCP发送一报文段时,它同时也在自己的重传队列中存放这个报文段的一个副本。若收到确认,则删除此副本。若在规定时间内没有收到确认,则重传此报文段的副本。TCP 的确认并不保证数据已交付给了应用进程,而只是表明在接收方的TCP已按序正确收到了对方所发送的报文段。

通信中的每一方都不必专门发送确认报文段,而可以在传送数据时顺便把确认信息捎带传送。为此,TCP 采用了一种延迟确认的机制,即接收方在正确接收到数据时可能要等待一小段时间(一般不超过 0.5s)再发送确认。若这段时间内有数据要发送给对方,则可以捎带确认。也有可能在这段时间内又有数据到达,则可以同时对这两次到达的数据进行累积确认。这样做可以减少发送完全不带数据的确认报文段,以提高 TCP的传输效率。

接收方若收到有差错的报文段就丢弃(不发送否认信息)。若收到重复的报文段,也要丢弃,但要立即发回确认信息;防止发送方没收到确认而超时重传。

若收到的报文段无差错,只是未按序号顺序到达,那么应如何处理?在 GBN 中会丢弃所有未按序到达的分组,但是TCP对此未做明确规定,而是让TCP的实现者自行确定。可以像GBN一样将不按序的报文段丢弃,但多数TCP实现是先将其暂存于接收缓存内,待所缺序号的报文段收齐后再一起上交应用层。在互联网环境中,封装TCP报文段的IP数据报不一定是按序到达的,将失序的报文段先缓存起来可以避免不必要的重传。注意,不论采用哪种方法,接收方都要立即对已按序接收到的数据进行确认。

TCP发送方每发送一个报文段,就会为这个报文段设置一个计时器。只要计时器设置的重传时间已经到了但还没有收到确认,就要重传这一报文段。

2.以字节为单位的滑动窗口

TCP采用滑动窗口协议提高传输效率。但与GBN不同的是,TCP发送窗口大小的单位是字节,而不是分组数。TCP发送方已发送的未被确认的字节数不能超过发送窗口的大小。下图形象的说明这个过程:

image-20210806155148202

实际上,TCP的发送窗口是会不断变化的。初始值在连接建立时由双方商定,TCP的流量控制和拥塞控制会根据情况动态地调整发送窗口上限值(可增大或减小),从而控制发送数据的平均速率。

下图a展示发送应用程序的缓存情况。发送缓存用来暂时存放:

(1)发送应用程序传送给发送方TCP准备发送的数据;

(2)TCP已发送出去但尚未收到确认的数据。

发送窗口通常只是发送缓存的一部分。已被确认的数据应当从发送缓存中删除,因此发送缓存和发送窗口的后沿是重合的。

image-20210806155340006

上图b展示接收应用程序的缓存情况。接收缓存用来暂时存放:

(1)按序到达的,但尚未被接收应用程序读取的数据;

(2)未按序到达的,但还不能被接收应用程序读取的数据。

如果收到的分组被检测出有差错,则要丢弃。

3.超时重传时间的选择

TCP的发送方在规定的时间内没有收到确认就要重传已发送的报文段。如何选择超时重传的时间是TCP中非常重要也是较复杂的一个问题。选择超时重传在数据链路层并不困难,但在运输层却不那么简单,因为传输质量太不确定了。

显然超时重传时间应比当前报文段的往返时间(Round-Trip Time,RTT)要长一些。针对互联网环境中端到端的时延是动态变化的特点,TCP采用了一种自适应算法。该算法记录每一个报文段发出的时间,以及收到相应的确认报文段的时间。这两个时间之差就是报文段的往返时间RTT。实际的RTT测量值变化非常大,因此需要用多个RTT测量值的平均值来估计当前报文段的RTT。这里的算法比较复杂,不做过多讨论。

4.快速重传

有时一个报文段的丢失会引起发送方连续收到多个重复的确认,通过收到多个重复的确认可以快速地判断报文段可能已经丢失而不必等待重传计时器超时。快速重传就是基于该方法对超时触发重传的补充和改进。如下图所示:

image-20210806174133062

假定发送方发送了报文段 M1~M5共5 个报文段。接收方每收到一个报文段后都要立即发出确认。当接收方收到了M1后,发出对M1的确认。假定由于网络拥塞使 M2丢失了。接收方后来收到下一个M3,发现其序号不对,但仍收下放在缓存中,同时发出对最近按序接收的 M1确认(请注意,不能对 M3确认,因为 TCP是累积确认,如果对 M3确认就表示 M2也已经收到了)。当接收方收到 M4和M5后,也还要分别发出对M1的重复确认。快速重传算法规定,发送方只要一连收到三个重复的确认,就应立即重传丢失的报文段M2(注意:重复确认的确认号正是要重传的报文段的序号),而不必继续等待为M2设置的重传计时器的超时。不难看出,快速重传并非取消重传计时器,而是尽早重传丢失的报文段。

5.选择确认

根据前面的讨论,我们知道TCP报文段的确认字段是一种累积确认,就是说,它只通告收到的最后一个按序到达的字节,而没有通告所有收到的失序到达的那些字节,虽然这些字节已经被接收方接收并暂存在接收缓存中。这些没有被确认的字节很可能因为超时而被发送方重传。为避免这些无意义的重传,一个可选的功能选择确认(Selective ACK,SACK)[RFC 2018]可以用来解决这个问题。选择确认允许接收方通知发送方所有正确接收了的但是失序的字节块,发送方可以根据这些信息只重传那些接收方还没有收到的字节块,这很像前面介绍的选择重传SR的工作方式。

假如有下面的传输报文段:

image-20210806192806253

接收方要将这些接收到的失序字节块通告给对方,只使用一个确认号是办不到的。每一个字节块需要用两个边界序号来表示,TCP的固定首部中没有哪个字段能提供上述这些字节块的边界信息,因此TCP在首部中提供了一个可变长的“SACK选项字段”来存放这些信息。

除此之外,要使用选择确认功能,在建立 TCP 连接时,双方还要分别在 SYN 报文段和 SYN+ACK 报文段的首部选项中都添加“允许 SACK 选项字段”,表示都支持选择确认功能。之后,才能在数据传输阶段使用 SACK选项字段进行选择确认。

当使用选择确认时,TCP首部中的“确认号字段”的功能和意义并没有改变,实际上SACK是对原来累积确认功能的一种补充,并可以和使用累积确认的超时重传与快速重传机制一起工作。目前多数TCP实现都支持选择确认功能。

TCP 的流量控制

如果应用程序读取数据比较慢,而发送方发送数据很快、很多,则很容易使该连接的接收缓存溢出。TCP 为应用程序提供了流量控制(flow control)服务,以解决因发送方发送数据太快而导致接收方来不及接收,使接收方缓存溢出的问题。

流量控制的基本方法就是接收方根据自己的接收能力控制发送方的发送速率。可以说流量控制是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读速率相匹配。利用滑动窗口机制可以很方便地控制发送方的平均发送速率。TCP采用接收方控制发送方发送窗口大小的方法来实现在TCP连接上的流量控制。

接收方根据接收缓存中可用缓存的大小,随时动态地调整对方的发送窗口上限值(可增大或减小)。为此,TCP 接收方要维持一个接收窗口的变量,其值不能大于可用接收缓存大小。在TCP报文段首部的窗口字段写入的数值就是当前接收方的接收窗口大小。TCP发送方的发送窗口的大小必须小于该值。

这里有个示例见下图:图中大写的ACK表示首部中的ACK位,小写的ack表示确认号字段的值。

image-20210806193709684

这里主机B进行了三次流量控制。第一次把窗口减小为300字节,第二次又把窗口减为100字节,最后把窗口减至0,即不允许对方再发送数据了。当接收方的接收缓存可用空间大小不再为0时,会主动将更新的窗口值发送给发送方。为防止因接收方发送给发送方的窗口变更报文段的丢失所导致的死锁状态,当窗口变为0时,如果发送方有数据要发送,则会周期性地(例如,60s)发送只包含1个字节数据的窗口探测(window probe)报文段,以便强制接收方发回确认并通告接收窗口大小。如果这时接收窗口大小非零,则会接收这个字节并对该字节进行确认,否则会丢弃该字节并对以前数据进行重复确认。

TCP 的连接管理

TCP连接就有三个阶段,即连接建立、数据传送和连接释放。

1.TCP的连接建立

在连接建立过程中要解决以下三个问题:

  1. 要使每一方能够确知对方的存在;
  2. 要允许双方协商一些参数(如最大报文段长度,最大窗口大小,服务质量等);
  3. 能够对运输实体资源(如缓存大小,各状态变量,连接表中的项目等)进行分配和初始化。

连接过程示意图:

image-20210806195940704

  • 主机A的TCP向主机B的TCP发出连接请求报文段,其首部中的同步位SYN应置1,同时选择一个序号 seq=x,这表明下一个报文段的第一个数据字节的序号是 x+1。
  • 主机B的TCP收到连接请求报文段后,如同意,则发回连接请求确认。在确认报文段中应把SYN 位和 ACK 位都置 1,确认号是 ack=x+1,同时也为自己选择一个序号 seq=y。
  • 主机A的TCP收到B接受连接请求的确认后,还要向B给出确认,其ACK置1,确认号ack=y+1,而自己的序号 seq=x+1。

TCP 的标准规定,SYN=1 的报文段(例如,A 发送的第一个报文段)不能携带数据,但要消耗掉一个序号。因此A发送的第二个报文段的序号应当是第一个报文段的序号加 1(虽然在第一个报文段中并没有数据)。注意,A 发送的第二个报文段中 SYN是0而不是1,ACK位必须为1。该报文段是对B的同步报文段的确认,但是一个普通报文段,可携带数据。若该报文段不携带数据,则不消耗序号。

运行客户进程的主机A的TCP通知上层应用进程,连接已经建立。当运行服务器进程的主机B的TCP收到主机A的确认后,会通知其上层应用进程,连接已经建立。连接建立采用的这种过程叫作三次握手(three-way handshake)。

2.TCP的连接释放

在数据传输结束后,通信的双方都可以发出释放连接的请求。在连接释放过程中要释放为该连接分配的所有资源。

image-20210809135012229

上图中主机A的应用进程先向其TCP发出连接释放请求,并且不再发送数据。TCP通知对方要释放从 A 到 B 这个方向的连接,把发往主机 B 的报文段首部的 FIN 置 1,其序号 seq=u。由于FIN报文段要消耗一个序号,因此序号u等于A前面已传送过的数据的最后一个字节的序号加1。

主机 B 的 TCP 收到释放连接通知后即发出确认,确认号是 ack=u+1,而这个报文段自己的序号假定为v(v等于B前面已传送过的数据的最后一个字节的序号加1)。主机B的TCP这时应通知高层应用进程,见上图中的箭头。这样,从A到B的连接就释放了,连接处于半关闭(half-close)状态,相当于主机A向主机B说:“我已经没有数据要发送了。但你如果还发送数据,我仍可以接收。”

此后,主机B不再接收主机A发来的数据。但若主机B还有一些数据要发往主机A,则可以继续发送(这种情况很少)。主机A只要正确收到数据,仍应向主机B发送确认。

若主机B不再向主机A发送数据,其应用进程就通知TCP释放连接,见上图中的箭头。主机 B 发出的连接释放报文段必须使 FIN=1,并使其序号仍为 v(因为前面发送的确认报文段不消耗序号),还必须重复上次已发送过的确认号 ack=u+1。主机 A 必须对此发出确认,把ACK 置 1,确认号 ack=v+1,而自己的序号是 seq=u+1(因为根据 TCP 标准,前面发送过的FIN报文段要消耗一个序号)。这样才把从B到A的反方向连接释放掉。

客户或服务端主动发送的SYN或FIN报文都需要消耗1个序号,而不捎带任何数据的确认ACK报文不消耗序号。

但此时,主机A的TCP并不能马上释放整个连接,还要再等待一个超时时间才能将整个连接释放。因为主机A的确认有可能丢失,这时B会重传FIN报文段。在这段超时时间内,若A又收到B重传的FIN报文段,A需要再次进行确认。收到A的最后确认,B才能最终将整个连接释放。若等待的这段超时时间内没有收到B的FIN报文段,主机A的TCP则向其应用进程报告,整个连接已经全部释放。

上述的连接释放过程是四次挥手,结束了。也可以看成是两个二次握手。

3.TCP的有限状态机

为了更清晰地看出TCP连接的各种状态之间的关系,下图给出了TCP连接管理的有限状态机。图中每一个方框即TCP可能具有的状态。状态之间的箭头表示可能发生的状态变迁。箭头旁边的字表明是什么原因引起这种变迁,或表明发生状态变迁后又出现什么动作。

有三种不同的箭头。粗实线箭头表示对客户进程的正常变迁(典型变迁)。粗虚线箭头表示对服务器进程的正常变迁。另一种细线箭头表示非典型变迁。一个TCP连接有两个端点。在TCP的有限状态机中,同时表示这两个端点的状态。

如下图所示,我们从连接还未建立时的关闭状态CLOSED开始。

建立连接

主机的客户进程发起连接请求(主动打开),这时本地 TCP 实体就创建传输控制模块 TCB,发送一个 SYN=1 的报文,因而进入SYN_SENT状态;当收到来自服务进程的SYN和ACK时,TCP就发送出三次握手中的最后的一个ACK,接着就进入连接已经建立的状态ESTABLISHED。这时就可以发送和接收数据了。

释放连接

运行客户进程主机的本地TCP实体发送 FIN=1 的报文,等待确认 ACK 的到达。这时状态变为 FIN_WAIT_1(见主动关闭的虚线方框中左上角);当收到确认ACK时,客户到服务的连接就关闭了,状态变为FIN_WAIT_2。当收到运行服务器进程的主机发送的 FIN=1 的报文后,响应确认ACK。这时服务到客户的连接也关闭了。但是TCP还要等待一段时间(报文段在网络中寿命的两倍),才删除原来建立的连接,返回到初始的CLOSED状态。这是为了保证原来连接上面的所有分组都从网络中消失。

image-20210806193451594

现在从服务器进程来分析状态图的变迁(看图中粗虚线箭头)。

建立连接

服务器进程发出被动打开,进入LISTEN状态。当收到 SYN=1 的连接请求报文后,发送确认 ACK,并使报文中的 SYN=1,然后进入SYN_RCVD状态。在收到三次握手中的最后一个确认ACK时,就转为ESTABLISHED状态,进入数据传送阶段。

释放连接

当客户进程的数据已经传送完毕,就发送出 FIN=1 的报文给服务器进程(见标有被动关闭的虚线方框),进入 CLOSE_WAIT 状态。服务器进程发送 FIN 报文段给客户进程,状态变为LAST_ACK状态。当收到客户进程的ACK时,服务器进程就释放连接,删除连接记录,状态回到原来的CLOSED状态。

还有一些状态变迁,如连接建立过程中的从 LISTEN 到 SYN_SENT 和从 SYN_SENT 到SYN_RCVD。

下图是另外一本经典书籍:《UNIX网络编程》中的插图,几乎一模一样,供参考。

image-20210811105534672

拥塞控制

当网络中出现太多的分组时,网络的性能开始下降。这种情况称为拥塞(congestion)。

拥塞的原因与危害

下图中的横坐标是输入负载或网络负载,代表单位时间内输入给网络的分组数目。纵坐标是吞吐量(throughput),代表单位时间内从网络输出的数据量。理想情况下,在吞吐量饱和之前,网络吞吐量应等于输入负载,故吞吐量曲线是45°的斜线。但当输入负载超过网络容量时,在理想情况下,吞吐量不再增长而保持为水平线,即吞吐量达到饱和。这就表明输入负载中有一部分损失掉了(例如,输入到网络的某些分组被路由器丢弃了)。

但是,在实际的网络中,若不采取有效的拥塞控制手段,随着输入负载的增大,网络吞吐量的增长速率逐渐减小。特别是当输入负载达到某一数值时,网络的吞吐量反而随输入负载的增大而下降,这时网络就进入了拥塞状态。当输入负载继续增大时,网络的吞吐量甚至有可能下降到零,即网络已无法工作。这就是所谓的死锁(deadlock)。

image-20210811133736148

拥塞控制的基本方法

拥塞控制和流量控制之间的区别。拥塞控制的任务是防止过多的数据注入到网络中,使网络能够承受现有的网络负载。这是一个全局性的问题,涉及各方面的行为,包括所有的主机、所有的路由器、路由器内部的存储转发处理过程,以及与降低网络传输性能有关的所有因素。流量控制只与特定点对点通信的发送方和接收方之间的流量有关。

从控制论的角度出发,拥塞控制可以分为开环控制和闭环控制两大类。开环控制方法试图用良好的设计来解决问题,它的本质是从一开始就保证问题不会发生。一旦系统启动并运行起来了,就不需要中途做修正。

相反,闭环控制是一种基于反馈环路的方法,其包括三个部分:

  1. 监测网络系统以便检测到拥塞在何时、何地发生;
  2. 把拥塞发生的信息传送到可以采取行动的地方;
  3. 调整网络系统的运行以解决出现的问题。

TCP 的拥塞控制

TCP采用的方法是让每一个发送方根据所感知到的网络拥塞的程度,来限制其向连接发送流量的速率。如果一个TCP发送方感知从它到目的地之间的路径上没有拥塞,则增加其发送速率(充分利用可用带宽);如果该发送方感知在该路径上有拥塞,则降低其发送速率。

总结

  • 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫作拥塞。拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。
  • 流量控制是一个端到端的问题,是接收端抑制发送端发送数据的速率,以便使接收端来得及接收。拥塞控制是一个全局性的过程,涉及所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。
  • 当网络拥塞而丢弃分组时,该分组在其经过路径中所占用的全部资源(如链路带宽)都被白白浪费掉了。
  • 为了进行拥塞控制,TCP 的发送方要维持一个拥塞窗口 cwnd 的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接收窗口中较小的一个。
  • TCP 的拥塞控制主要包括 3 种算法:慢启动、拥塞避免和快速恢复。

(完)