计算机网络(三)

第3章 运输层

3.1 概述和运输层服务

运输层协议为运行在不同主机上的应用进程之间提供了 逻辑通信(logic communication) 功能。

运输层协议是在端系统中而不是在路由器中实现的。在发送端,运输层将发送应用程序进程发来的报文转换为运输层分组,称为运输层 报文段(segment) 。实现的方法是将应用报文划分为较小的块,并为每块加上一个运输层首部以生成运输层报文段。然后,在发送端系统中,运输层将这些报文段传递给网络层,网络层将其封装成网络层分组(即数据报)并向目的地发送。

注意:网络路由器仅作用于该数据报的网络层字段;不检查封装在该数据报的运输层报文段的字段。在接收端,网络层从数据包中提取运输层报文段,并将该报文段上交给运输层。运输层处理接收到的报文段,使其中数据为接收应用进程所使用。

3.1.1 运输层和网络层的关系

网络层提供了 主机 之间的逻辑通信,而运输层为运行在不同主机上的进程之间提供了逻辑通信。

运输层协议只工作在端系统中。在端系统中,运输层协议将来自应用进程的报文移动到网络边缘(即网络层),反过来也一样,但对有关这些报文在网络核心中如何移动并不做任何规定。

运输协议能够提供的服务常常受制于底层网络层协议的服务模型。如果网络层协议无法为主机之间发送的运输层报文段提供时延好或带宽保证的话,运输层协议就无法为进程之间发送的应用程序报文提供时延或带宽保证。但是,即使底层完了过协议不能在网络层提供相应的服务,运输层协议也能提供某些服务。例如,即使底层网络协议是不可靠的,也就是说网络层协议会使分组丢失、篡改和冗余,运输协议也能为应用程序提供可靠的数据传输服务。

举例说明,两家人住的很远,需要写信沟通, 每家有一个孩子负责收家里的信件交给邮政,接收邮政信件分发给家人。

用上述例子中的人物类比网络的运行:

应用层报文 = 信封上的字符

进程 = 写信的每个人

主机(又称端系统) = 家庭

运输层协议 = 每家负责收发信件的人

网络层协议 = 邮政服务

3.1.2 因特网运输层概述

简要介绍一下网络层协议。因特网网络层协议有一个名字叫做 IP ,即网际协议。IP 为主机之间提供了逻辑通信。IP 的服务模型是 尽力而为交付服务(best-effort delivery service) 。它不确保报文段的交付,不保证报文段的按序交付,不保证报文段中数据的完整性。

运输层协议 UDP 和 TCP 最基本的责任是,将两个端系统间 IP 的交付服务扩展为运行在端系统上的两个进程之间的交付服务。将主机间交付扩展到进程间交付被称为 运输层的多路复用(transport-layer multiplexing)多路分解(demultiplexing)

进程到进程的数据交付和差错检查是两种最低限度的运输层服务,也是 UDP 所能提供的仅有的两种服务。

TCP 为应用程序提供了几种附加服务:

  • 可靠数据传输(reliable data transfer)

  • 拥塞控制(congestion control)

    TCP 力求为每个通过一条拥塞网络链路的连接平等地共享网络链路带宽。

    UDP 流量是不可调节的。使用 UDP 传输的应用程序可以根据其需要以其愿意的任何速率发送数据。

3.2 多路复用和多路分解

一个进程(作为网络应用的一部分)有一个或多个 套接字(socket) ,它相当于从网络向进程出传递数据和从进程向网络传递数据的门户。在接收主机中的运输层实际上并没有直接将数据交付给进程,而是将数据交给了一个中间的套接字。

多路分解(demultiplexing) 将运输层报文段中的数据交付到正确的套接字。

多路复用(multiplexing) 源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息(这将在以后用于分解)从而生成报文段,然后将报文段传递到网络层。

继续上面的例子,

从邮递员处收到一批信件,并通过查看收信人名字将信件亲手交付给家人门的过程执行的是分解操作。

从家人们手中收集信件交付给邮递员时,执行的是多路复用操作。

运输层多路复用的要求:

  1. 套接字有唯一的标识符;
  2. 每个报文段有特殊字段来指示该报文段所要交付到的套接字。(特殊字段是 源端口号字段(source port number field)目的端口号字段(destination port number field) )。(UDP 和 TCP 报文段还有一些其他的字段。)

端口号是一个 16 比特的数,其大小在 0~65535 之间。0~1023 范围的端口号称为 周知端口号(well-known port number) ,是受限制的,是保留给注入 HTTP (端口号 80) 和 FTP (端口号 21) 之类的周知应用层协议使用。

当我们开发一个新的应用程序时,必须为其分配一个端口号。

1. 无连接的多路复用与多路分解

clientSocket = socket(AF_INET, SOCK_DGRAM)

当用这种方式创建一个 UDP 套接字时,运输层自动地为该套接字分配一个端口号。特别是,运输层从范围 1024~65535 内分配一个端口号,该端口号是当前未被该主机中任何其他 UDP 端口使用的号。

clientSocket.bind(('', 19157))

另一种方法是,再创建一个套接字后,通过套接字 bind() 方法为这个 UDP 套接字关联一个特定的端口号(如 19157)。

一个 UDP 套接字是由一个二元组全面标识的,该二元组包含一个目的 IP 地址和一个目的端口号。

即使两个 UDP 报文段有不同的源 IP 地址和(或)源端口号,但具有相同的目的 IP 地址和目的端口号,那么这两个报文段将通过相同的目的套接字被定向到相同的目的进程。

服务器可以使用 recvfrom() 方法从接收到的报文段中提取出客户端(源)端口号,然后,它将所提取的源端口号作为目的端口号,向客户发送一个新的报文段。

2. 面向连接的多路复用与多路分解

TCP 套接字是由一个四元组(源 IP 地址,源端口号,目的 IP 地址,目的端口号)来标识的。

当一个 TCP 报文段从网络到达一台主机时,该主机使用全部 4 个值来将报文段定向(分解到)相应的套接字。特别与 UDP 不同的是,两个具有不同源 IP 地址或源端口号的 TCP 报文段将被定向到两个不同的套接字,除非 TCP 报文段携带了初始创建连接的请求。

再次理解 SOCKET 编程中 TCP 客户—服务器例子:

  • TCP 服务器应用程序有一个 “欢迎套接字” ,它在 12000 号端口上等待来自 TCP 客户的连接建立请求。

  • TCP 客户使用下面代码创建一个套接字并发送一个连接建立请求报文段:

clientSocket = socket(AF_INET, SOCK_STREAM)

clientSocket.connect((serverName, 12000))

  • 一条连接建立请求是一个目的端口号为 12000,TCP 首部的特定“连接建立位”置位的 TCP 报文段。该报文段包含一个有客户选择的源端口号。

  • 当运行服务器进程的计算机的主机操作系统收到具有目的端口 12000 的入连接请求报文段后,它就定位服务器进程,该进程正在端口号 12000 等待接受连接。该服务器进程则创建一个新的套接字:

connectionSocket, addr = serverSocket.accept()

  • 该服务器在运输层查看连接请求报文段中的 TCP 套接字信息,如果 4 个值都匹配,则被分解到这个套接字中。随着 TCP 连接完成,客户和服务器便可相互发送数据了。

如下图,即便目的端口号相同,只要源 IP 和源端口号之一不同,即可区分不同的 TCP 套接字。

3. Web 服务器与 TCP

一台 Web 服务器为每条连接生成一个新进程。如上图 3-5 ,每个这样的进程都有自己的连接套接字,通过这些套接字可以收到 HTTP 请求和发送 HTTP 响应。

然而,连接套接字与进程之间并非总是一一对应的关系。当今的高性能 Web 服务器通常只使用一个进程,但是为每个新客户连接创建一个具有新连接套接字的新线程。(线程可被看作是一个轻量级的子进程。)

3.3 无连接运输:UDP

UDP 从应用进程得到数据,附加上用于多路复用/分解服务的源和目的端口号字段,以及两个其他的小字段,然后将形成的报文段交给网络层。网络层将该运输层报文段封装到一个 IP 数据报中,然后尽力而为地尝试将此报文段交付给接收主机。如果该报文段到达接收主机,UDP 使用目的端口号将报文段中地数据交付给正确的应用进程。

使用 UDP 时,在发送报文段之前,发送方和接收方的运输层实体之间没有握手,所以称 UDP 是 无连接的

DNS 是一个通常使用 UDP 的应用层协议的例子。

数据传输中 TCP 不总是首选,因为有许多应用更适合用 UDP ,原因如下:

  1. 关于发送什么数据以及何时发送的应用层控制更为精细。事实应用通常要求最小的发送速率,不希望过分地延迟报文段地传送,且能容忍一些数据丢失,TCP 服务模型并不是特别适合这些应用的需要。这些应用可以使用 UDP ,并将作为应用的一部分来实现所需的、超出 UDP 的不提供不必要报文段交付服务之外的额外功能。
  2. 无须连接建立。UDP 无需任何准备即可进行数据传输,因此不会引入建立连接的时延。这也是 DNS 运行在 UDP 之上而不是 TCP 上的主要原因。HTTP 使用 TCP 是因为对于具有文本数据的 Web 网页来说,可靠性是至关重要的。用于谷歌的 Chrome 浏览器中的 QUIC 协议(快速 UDP 因特网连接)将 UDP 作为其支撑运输协议并在 UDP 之上的应用层协议中实现可靠性。
  3. 无连接状态。UDP 不维护连接状态(接收和发送缓存),也不跟踪参数(拥塞控制参数以及序号与确认号的参数)。
  4. 分组首部开销小。每个 TCP 报文段都有 20 个字节的首部开销,而 UDP 仅有 8 字节。

电子邮件、远程终端访问、Web 以及文件传输都运行在 TCP 之上。因为所有这些应用都需要 TCP 的可靠数据传输服务。

UDP 用于承载网络管理数据(SNMP)。此时,UDP 要优于 TCP,因为网络管理应用程序通常必须在该网络处于重压状态时运行,而正是在这个时候可靠的、拥塞受控的数据传输难以实现。所以 DNS 运行在 UDP 之上,从而避免了 TCP 连接创建时延。

当分组丢包率低时,为了安全,某些机构阻塞 UDP 流量,对于流式媒体传输来说,TCP 变的越来越有吸引力了。

使用 UDP 的应用时可能实现可靠数据传输的。这可通过在应用程序自身中建立可靠性机制来完成(例如,可通过增加确认与重传机制来实现)。谷歌的 Chrome 浏览器用的 QUIC 协议就是在 UDP 的应用层协议中实现了可靠性。

3.3.1 UDP 报文段结构

长度字段指明了包括首部在内的 UDP 报文段长度(以字节为单位)。

检验和: 接收方用检验和来检测在该报文段中是否出现了差错。

3.3.2 UDP 检验和

发送方的 UDP 对报文段中的所有 16 比特字求和,溢出回卷(即高位进位的话加在低位),对和求反,得到检验和。

接受方处报文段中所有 16 比特字求和,再与检验和相加,如果结果全为 1 ,则没有出现差错,有一位出现 0 则该分组中已经出现差错。

举例如下:

假定有下面三个比特的字:

再既无法确保链路的可靠性,又无法确保内存中的差错检测的情况下,如果端到端数据传输服务要提供差错检测,UDP 就必须在端到端基础上在运输层提供差错检验。这时一个在系统设计中被称颂的 端到端原则(end-end principle) 的例子,该原则表述为在系统设计中因为某种功能(在此时为差错检测)必须基于端到端实现:”与在较高级别提供这些功能的代价相比,在较低级别上设置的功能可能是冗余的或几乎没有价值的。“

虽然 UDP 提供差错检测,但对差错恢复无能为力。UDP 的某种实现只是丢弃受损的报文段;其他实现是将受损的报文段交给应用程序并给出警告。

3.4 可靠数据传输原理

一般场景下考虑可靠数据传输问题。

下图说明了可靠数据传输的框架。为上层实体提供的服务抽象是:数据可以通过一条可靠的信道进行传输。借助于可靠信道,传输数据比特就不会受到损坏(由 0 变为 1 ,或者相反)或丢失,而且所有数据都是按照其发送顺序进行交付。这恰好就是 TCP 向调用它的因特网应用所提供的服务模型。

实现这种服务抽象是 可靠数据传输协议(reliable data transfer protocol) 的责任。

除了交换含有待传输的数据的分组之外,rdt 的发送端和接收端还需往返交换控制分组(请求与响应的交互)。rdt 的发送端和接收端都要通过调用 udt_send() 发送分组给对方(udt 表示不可靠数据传输)。

3.4.1 构造可靠数据传输协议

下面利用有限状态自动机逐步研究更为复杂的的协议,最后得到一个完美、可靠的数据传输协议。

首先介绍一下 有限自动机(Finite-State Machine,FSM) 的图例,引起变迁的事件显示在表示变迁的横线上方,事件发生时所采取的动作显示在横线下方。将在横线上方或下发使用符号 ,以分别明确地表示缺少动作或事件。初始状态用虚线表示。

下面研究 停等协议(stop-and-wait protocol)

1. 经完全可靠信道的可靠数据传输:rdt1.0

​ rdt 的发送端只通过 rdt_send(data) 事件接收来自较高层的数据,产生一个包含该数据的分组(经由 make_pkt(data) 动作),并将分组发送到信道中。实际上,rdt_send(data) 事件是由较高层应用的过程调用产生的(例如, rdt_send())。

​ 在接收端,rdt 通过 rdt_rcv(packet) 事件从底层信道接收一个分组,从分组中取出数据(经由 extract(packet, data) 动作),并将数据上传给较高层(通过 deliver_data(data) 动作)。实际上,rdt_rcv(packet) 事件是由较低层协议的过程调用产生的(例如,rdt_rcv())。

2. 经具有比特差错信道的可靠数据传输:rdt2.0

网络物理部件中会出现比特差错,还是假设所有发送的分组(虽然比特可能受损)将按其发送的顺序被接收。

利用 肯定确认否定确认 这种控制报文使得接收方可以让发送方知道哪些内容被正确接收,哪些内容接收有误并需要重传。基于这样重传机制的可靠数据传输协议称为 自动重传机制(Automatic Repeat reQuest,ARQ)协议

ARQ 协议中还需要另外三种协议功能来处理存在比特差错的情况:

  • 差错检测。
  • 接收方反馈。从接收方向发送方回送 ACK 与 NAK 分组。
  • 重传。接收方收到有差错的分组时,发送方将重传该分组文。

rdt2.0 这样的协议又被称为 停等协议(stop-and-wait) ,是因为只有当发送方确信接收方已经正确接收当前分组,发送方才会发送一块新数据。

但是上面这种协议,没有考虑到,如果 ACK 或 NAK 分组受损的可能性!

考虑处理受损 ACK 和 NAK 时的 3 种可能性:

  1. 如果 ACK 和 NAK 受损,要求接收方重发一次,但是发出要求的报文也可能受损,通信将陷入混乱。
  2. 增加足够的检验和比特,使发送方不仅可以检测差错,还可以恢复差错。
  3. 当发送方收到含糊不清的 ACK 或 NAK 分组时,只需重传当前数据分组即可。然而,这种方法在发送方到接收方的信道种引入了 冗余分组(duplicate packet) 。冗余分组的根本困难在于接收方不知道它上次所发送的 ACK 或 NAK 是否被发送方正确地收到。因此它无法事先知道接收到的分组时新的还是一次重传。

此处利用第三种可能方法来解决 ACK 或 NAK 受损的问题。为了让接收方能确定接收到的分组是新的还是一次重传,在数据分组中添加一新字段,让发送方对其数据分组编号,即将发送数据分组的 序号(sequence number) 放在该字段。

对停等协议这种简单情况,1 比特序号就够了。只需要判断刚收到的分组序号与最近确定的分组序号不同,则接收到了新的分组,如果序号相同则为重传分组。实现的 FSM 如下:

或将 ACK 的 0,1 直接打包在报文中

3. 经具有比特差错的丢包信道的可靠数据传输:rdt3.0(停等协议)

现在假设除了比特受损外, 底层信道还会丢包。

实践中采取的方法是:发送方明智的选择一个时间值,以判定可能发生丢包(尽管不能确保)。如果在这个时间内没有收到 ACK,则重传分组。

无论是 ACK 丢失,或者只是 分组或 ACK 过度延时,都只需要重传即可解决。

为了实现重传机制,需要一个 倒计数定时器(countdown timer) ,在一个给定的时间量过期后,可中断发送方。发送方需要做到:

  1. 每次发送一个分组(包括第一次分组和重传分组)是,便可启动一个定时器。
  2. 响应定时器中断(采取适当的动作)。
  3. 终止定时器。

因为分组序号在 0 和 1 之间交替,因此 rdt3.0 又是被称为 比特交替协议(alternationg-bit protocol)

3.4.2 流水线可靠数据传输协议

rdt3.0 信道利用率很低,性能问题的核心在于它是一个停等协议。

流水线技术可用来大幅提升可靠数据传输协议的性能:

由上图可以看出,流水线技术对可靠数据传输协议可带来如下影响:

  • 必须增加序号范围,每个输送中的分组(重传的除外)必须有一个唯一的序号,而且也许有多个在输送中未确认的报文。
  • 协议的发送方需要缓存以发送但是还没有确认的分组。接收方需要缓存那些已经正确接收的分组。
  • 不同的解决方法对序号范围和缓冲要求不同。解决流水线的差错恢复有两种基本方法: 回退 N 步(Go-Back-N,GBN)选择重传(Selective Repeat,SR)

3.4.3 回退 D 步(GBN,又称滑动窗口协议)

在 GBN 协议中,允许发送方发送多个分组(当有多个分组可用时)而不需要等待确认,但它也受限于在流水线中未确认的分组数不能超过某个最大允许数 N 。

随着协议的运行,该窗口在序号空间向前滑动。N 常被称为 窗口长度(window size) ,GBN 协议也常被称为 滑动窗口协议(sliding-window protocol)

一个分组的序号承载在分组首部的一个固定长度的字段中。如果分组序号字段的比特数为 k,则该序号范围是 [0,2k1][0,2^k-1] 。在一个优点序号范围内,所有涉及序号的运算必须模 2k2^k 。TCP 有一个 32 比特的序号字段,其中的 TCP 序号就是按字节流中的字节进行计数的,而不是按分组计数。

GBN 发送方必须响应三种类型的事件:

  • 上层的调用。当上层调用 rdt_send() 时,发送方首先检查发送窗口是否已满,即是否有 N 个已发送但未被确认的分组。如果窗口未满,则产生一个分组并将其发送,并相应更新变量。如果窗口已满,发送方只需将数据返回给上层,隐式地指出上层该窗口已满。然后上层可能过一会儿再试。实际中,发送方更可能缓存这些数据,或者使用同步机制允许上层仅在当窗口不满时才能调用 rdt_send()
  • 收到一个 ACK 。在 GBN 协议中,对序号为 n 地分组采取 累积确认(cumulative acknowledgment) 的方式,表明接收方已正确接收到序号为 n 的以前包括 n 在内的所有分组。
  • 超时事件。协议称为”回退 N 步“来源于出现丢失和延时过长分组时发送方的行为。就像停等协议,定时器用于恢复数据或确认分组丢失。如果出现超时,发送方重传所有已发送但还未被确认过的分组。发送方仅使用一个定时器,它可被认为是最早的已发送但未被确认的分组所使用的定时器。如果收到一个 ACK ,但仍有已发送但未被确认的分组,则定时器被重新启动。如果没有已发送但未被确认的分组,停止该计时器。

在 GBN 种,接收方的动作也很简单。如果一个序号为 n 的分组被正确接收到,并且按需(即上次交付给上层的数据是序号为 n-1 的分组),则接收方为分组 n 发送一个 ACK,并将该分组中的数据部分交付到上层。在所有其他情况下,接收方丢弃该分组,并且为最近按序接收的分组重新发送 ACK 。注意到因为一次交付给上层一个分组,如果分组 k 已接收并交付,则所有序号比 k 小的分组也已经交付。因此,使用累计确认是 GBN 一个自然的选择。

3.4.4 选择重传(SR)

单个分组的差错就能够引起 GBN 重传大量分组,许多分组根本没有必要重传。

选择重传(SR)协议通过让发送方仅重传那些它怀疑在接收方出错(即丢失或损失)的分组而避免了不必要的重传。这种个别的、按需的重传要求接收方逐个地确认正确接收的分组。再次用窗口长度 N 来限制流水线中为完成、未被确认的分组数。

可以不按顺序到达,每接收或确认一个都标记一下,直到满足一次发送或上传数据的要求,则窗口滑动。

SR 发送方的事件与动作

  1. 从上层收到数据。当从上层收到数据后,SR 发送方检查下一个可用于该分组的序号。如果序号位于发送方的窗口内,则将数据打包并发送;否则就像在 GBN 中一样,要么将数据缓存,要么将其返回给上层以便以后传输。
  2. 超时。现在每个分组必须拥有其自己的逻辑定时器,因为超时发生后就只能发送一个分组。可以使用单个硬件定时器模拟多个逻辑定时器的操作。
  3. 收到 ACK。如果收到 ACK,若该分组序号在窗口内,则 SR 发送方将哪个被确认的分组标记为已接收。如果该分组的序号等于 send_base ,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且有序号落在窗口内未发送分组,则发送这些分组。

SR 接收方的事件与动作

  1. 序号在 [rcv_base,rec_base+N1][rcv\_base, rec\_base+N-1] 内的分组被正确接收。在此情况下,收到的分组落在接受方的窗口内,一个选择 ACK 被会送给发送方。如果该分组以前没有收到过,则缓存该分组。如果该分组的序号等于接收窗口的基序号,则该分组以及以前缓存的序号连续的分组交付给上层。例,当收到一个序号为 recv_base = 2 的分组时,如果已缓存了分组3、4、5,则将分组2、3、4、5 交付给上层。
  2. 序号在 [rcv_baseN,rec_base1][rcv\_base-N, rec\_base-1] 内的分组被正确接收到。在此情况下,必须产生一个 ACK,即使该分组是接收方以前已确认过的分组。(又收到之前的分组可能是发送方没有收到确认信号 ACK。)
  3. 其他情况。忽略该分组。

发送方和接收方的窗口并不总是一致的。

观察下图中的两种情况,它们是等同的。没有办法区分是第 1 个分组的重传还是第 5 个分组的初次传输。显然,窗口长度比序号空间小 1 时,协议无法工作。

对于 SR 协议,窗口长度必须小于或等于序号空间大小的一半。

对于分组排序,信道地一定时延可被看成基本上是在缓存分组,并且在将来任意时刻自然地释放出这些分组。由于序号可以被重新使用,那么可能会出现冗余分组。实际应用中采用地方法是,确保一个序号不被重新使用,直到发送方”确信“军人和先前发送的序号为 x 的分组都不再在网络中为止。通过假定一个分组在网络中的”存活“事件不会超过某个固定最大时间量来做到这一点。在高速网络的 TCP 扩展中,最长的分组寿命被假定为大约 3 分钟。

3.5 面向连接的运输:TCP

3.5.1 TCP 连接

由于 TCP 协议只在端系统中运行,而不在中间的网络元素(路由器和链路层交换机)中运行,所以中间的网络元素不会维持 TCP 的连接状态。

TCP 连接提供的是

  • 全双工(full-duplex service): 应用层数据可以从进程 A 流向进程 B,也可以从进程 B 流向进程 A。
  • 点对点(point-to-point): 两台主机是一对,不存在一对多传输数据。

客户进程通过套接字(该进程之门)传递数据流。数据一旦通过该门,TCP 将这些数据引导到该连接的 发送缓存(send buffer) 里,发送缓存是发起三次握手期间设置的缓存之一。

TCP 可从缓存中取出并放入报文段中的数据数量受限于 最大报文段长度(Maximum Segment Size,MSS)

注意:MSS 是指在报文段里应用层数据的最大长度,而非包括首部的 TCP 报文段的最大长度。

MSS 通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即所谓的 最大传输单元(Maximum Transmission Unit,MTU) )来设置。

MSS = MTU - TCP/IP首部长度(通常 40 字节)

以太网和 PPP 链路层协议都具有 1500 字节的 MTU,因此 MSS 的典型值为 1460 字节。

TCP 连接的组成包括,一台主机上的缓存、变量和进程连接的套接字,以及另一台主机上的另一组缓存、变量和于进程连接的套接字。

3.5.2 TCP 报文段结构

TCP 报文段由首部字段和一个数据字段组成。

TCP 首部一般是 20 字节(比 UDP 首部多 12 字节)。

TCP 把数据看成一个无结构的、有序的字节流。

一个报文段的序号就是该报文段数据字段首字节的序号。

确认号就是主机正在等待的数据的下一个字节序号。

3.5.3 往返时间的估计与超时

1. 估计往返时间

在任意时刻,仅为一个已发送的但目前尚未被确认的报文段估计 SampleRTT,从而产生一个接近每个 RTT 的新 SampleRTT 值。

TCP 维持一个 SampleRTT 的均值(称为 EstimatedRTT)。一旦获得一个新的 SampleRTT,TCP 就会根据下列公式来更新 EstimatedRTT:

EstimatedRTT=(1α)EstimatedRTT+αSampleRTTEstimatedRTT = (1-\alpha) * EstimatedRTT + \alpha * SampleRTT

用 DevRTT 估算 SampleRTT 偏离 EstimatedRTT 的程度:

DevRTT=(1β)DevRTT+βSampleRTTEstimatedRTTDevRTT = (1-\beta)*DevRTT +\beta * | SampleRTT - EstimatedRTT|

2. 设置和管理重传超时间隔

超时间隔应该大于等于 EstimatedRTT ,否则,将造成不必要的重传。但是超时间隔也不应该比 EstimatedRTT 大太多,否则当报文段丢失时,TCP 不能很快地重传该报文段,导致数据传输时延大。所以超时间隔设置为 EstimatedRTT 加上一定余量。

TimeoutInterval=EstimatedRTT+4DevRTTTimeoutInterval = EstimatedRTT + 4 * DevRTT

3.5.4 可靠数据传输

TCP 在 IP 不可靠的尽力而为服务之上创建了一种 可靠数据传输服务(reliable data transfer service)

超时机制外,还使用冗余确认技术。

1. 一些有趣的情况

图 3-34 中,当主机 B 收到重传报文段时,它将通过序列号发现该报文段包含了已收到的数据,即发往主机 A 的确认报文丢失了,TCP 丢弃该重传报文,重新发送 ACK。

图 3-35 中,当超时事件发生时,主机 A 重传序号 92 的第一个报文段,并重启定时器。只要第二个报文段的 ACK 在新的超时发生以前到达,则第二个报文段将不会被重传。

图 3-36 中,第一个报文段的确认报文在网络丢失,但在超时事件发生之前主机 A 收到一个确认号为 120 的确认报文。主机 A 因知道主机B 已经收到了序号为 119 及之前的所有字节,所以主机 A 不会重传这两个报文段中的任何一个。

2. 超时间隔加倍

每次 TCP 重传时都会将下一次的超时间隔设置为先前值得两倍(呈指数增长)。然而,当再次正常执行时,TimeoutInterval 由最近得 EstimatedRTT 值与 DevRTT 值推算得到。

在拥塞得时候,如果源持续重传分组,会使拥塞更加严重。相反,TCP 使用更文雅的方式,每个发送方的重传都是经过越来越长的额事件间隔后进行的。

3. 快速重传

冗余 ACK (duplicate ACK)就是再次请确认某个报文段的 ACK,而发送方先前已经收到对该报文段的确认。

一旦收到 3 个冗余 ACK,TCP 就执行 快速重传(fast retransmit) ,即在该报文段的定时器过期之前重传丢失的报文段。

4. 是回退 N 步还是选择重传

TCP 确认是累积式的,正确接收但失序的报文段是不会被接收方逐个确认的。TCP 发送方仅需维持已发送过但未被确认的字节的最小序号(SendBase)和下一个要发送的字节的序号(NextSeqNum)。

当发送方发送的一组报文段 1,2,…,N,对分组 n < N 分组的确认报文丢失,但其余 N - 1 个确认报文在分别超时以前到达发送端。

  • GBN 不仅会重传分组 n,还会重传所有后继的分组 n+1,n+2, …,N。

  • TCP 将重传至多一个报文段,即报文段 n。

    此外,如果对报文段 n+1 的确认报文在报文段 n 超时之前到达,TCP 甚至不会重传报文段 n。

3.5.5 流量控制

如果某应用程序读取数据时相对缓慢,而发送方发送得太多、太快,发送得数据就会很容易地使该连接的接收缓存溢出。

拥塞控制(congestion control):TCP 发送方可能因为 IP 网络的拥塞而被遏制。

拥塞控制与流量控制是针对完全不同的原因采取的措施。

TCP 通过让发送方维护一个称为 接收窗口(receive window) 的变量来提供流量控制。TCP 是全双工通信,在连接两端的发送方都各自维护一个接收窗口。

假设主机 A 通过一条 TCP 向主机 B 发送一个大文件。主机 B 为该连接分配了一个接收缓存,并用 RevBuffer 来表示其大小。主机 B 上的应用进程不时从该缓存中读取数据。

主机 B 通过把当前的 rwnd 值放入它发送给主机 A 的报文接收窗口字段中,通知主机 A 它在该连接缓存中还有多少可用空间。

当主机 B 的接收窗口为 0 时,主机 A 继续发送只有一个字节数据的报文段。这些报文段将会被接收方确认。最终缓存开始清空,B 发给 A 的确认报文里将包含一个非 0 的 rwnd 值。

UDP 不提供流量控制,报文段由于缓存溢出可能在接收方丢失。

3.5.6 TCP 连接管理

3.6 拥塞控制原理

流量控制:如果某应用程序读取数据时相对缓慢,而发送方发送得太多、太快,发送得数据就会很容易地使该 连接的接收缓存溢出

拥塞控制:丢包一般是当网络变得拥塞时,由于 路由器缓存溢出 引起的。

3.6.1 拥塞原因与代价

暂不关注如何对拥塞做出反应或避免拥塞,随着主机增加其发送速率并使网络变得拥塞,这时会发生的情况:

1. 情况 1:两个发送方和一台具有 无穷大 缓存的路由器

每连接的吞吐量(per-connection throughput):接收方每秒接收的字节数。

图 a 说明吞吐量的上限是由两条连接之间的共享链路容量造成的。

图 b 说明拥塞网络的一种代价是:当分组的到达速率接近链路容量时,分组经历巨大的排队时延。

2. 情况 2:两个发送方和一台具有 有限 缓存的路由器

λin\lambda_{in}' :运输层向网络中发送报文段(含有初始数据或重传数据)的速率。单位:字节/秒。

图 a 是没有丢包重传时的, λin=λin\lambda_{in} = \lambda_{in}' ,连接吞吐量就是 λin\lambda_{in}

图 b 为发送方仅当在确定了一个分组已经丢失时才重传。可以看到此时的一种网络拥塞代价是:发送方必须执行重传以补偿因为缓存溢出而丢弃的分组。

图 c 为发送方提前发生超时并重传在队列中已被推迟但还未丢失的分组。此时的一种网络拥塞代价是:发送方在遇到大时延时所进行的不必要的重传会引起路由器利用其链路带宽来转发不必要的分组副本。(由于每个分组被转发(平均)两次,档期供给载荷接近 R/2R/2 时,其吞吐量将渐进 R/4R/4 。)

3. 情况 3:4 个发送方和具有有限缓存的多台路由器及多跳路径

有 4 台主机发送分组,每台都通过交叠的两跳路径传输。

以主机 A 向主机 C 发送报文的吞吐量为例,可以看出当 λin\lambda_{in}' 较小时,随着 λin\lambda_{in}' 增大 λout\lambda_{out} 也增大。

而当流量很大时,A-C 流量就会和 B-D 流量竞争 R2 有限的缓存空间。B-D 流量到达速率将比 A-C 流量的到达速率大的多,所以在重载的极限情况下 A-C 端到端吞吐量将趋近于 0 。此时 A-C 流量在第一跳路由器 R1 上就做了无用功。

由于拥塞而丢弃分组的另一种代价是:当一个分组沿着一条路径被丢弃是,每个上游路由器用于转发该分组到丢弃该分组而使用的传输容量最终被浪费了。

3.6.2 拥塞控制方法

事件中所采用的两种主要拥塞控制方法:在最为宽泛的级别上,我们可根据网络层是否为运输层拥塞控制提供了显式帮助来区分拥塞控制方法:

  • 端到端拥塞控制。在端到端拥塞控制方法中,网络层没有为运输层拥塞控制提供显示支持。TCP 报文段的丢失(通过超时或 3 次冗余确认而得知)被认为式网络拥塞的一个迹象,TCP 会相应地减少其窗口长度。

  • 网络辅助的拥塞控制。路由器向发送方提供关于网络中拥塞状态的显式反馈信息。

    拥塞信息从网络反馈到发送方通常有两种方式:

    • 直接反馈信息可以由网络路由器发给发送方。通常使用 阻塞分组(choke packet) 的形式(主要是说:”我拥塞了!“)。
    • 更为通用的是,路由器标记或更新从发送方流向接收方的分组中的某个字段来只是拥塞的产生。一旦收到一个标记分组后,接收方就会向发送方通知该网络拥塞指示。(该形式的通知至少要经过一个完整的往返时间。)

3.7 TCP 拥塞控制

TCP 必须使用端到端拥塞控制而不是使用网络辅助的拥塞控制,因为 IP 层不向端系统提供显式的网络拥塞反馈。

TCP 所采用的方法是让每一个发送方根据所感知到的网络拥塞程度来限制其能向连接发送流量的速率。

TCP 发送方如何限制向其连接发送流量?

TCP 连接的每一端都是由一个接收缓存、一个发送缓存和几个变量(LastByteRead、rwnd 等)组成。运行在发送方的 TCP 拥塞控制机制跟踪一个额外的变量,即 拥塞窗口(congestion window) 。拥塞窗口表示为 cwnd,它对一个 TCP 发送方能向网络中发送流量的速率进行了限制。

在一个发送方中未被确认的数据量不会超过 cwnd 与 rwnd 中的最小值。

TCP 发送方如何感知在它与目的地之间的路径上出现了拥塞?

将一个 TCP 发送方的”丢包事件“定义为:要么出现超时,要么收到来自接收方的 3 个冗余 ACK。

当出现过度的拥塞时,在沿着这条路径上的一台(或多台)路由器的缓存会溢出,引起一个数据报(包含一个 TCP 报文段)被丢失。

TCP 发送方感知到端到端的拥塞时,采用何种算法来改变其发送速率?

TCP 使用确认来触发(或计时)增大它的拥塞窗口长度,TCP 被说成是 自计时(self-clocking) 的。

TCP 控制发送速率的目的是:既使得网络不会拥塞,与此同时又能充分利用所有可用的带宽:

  • 一个丢失的报文段意味着拥塞,因此当丢失报文段时应当降低 TCP 发送方的速率(减小它的拥塞窗口长度)。
  • 一个确认报文段指示该网络正在向接收方交付发送方的报文段,因此,对先前未确认的报文确认到达时,能够增加发送方的速率。
  • 带宽探测。给定 ACK 指示源到目的地路径无拥塞,而丢包事件指示路径拥塞,TCP 调节其传输速率的策略是增加其速率以响应到达的 ACK ,除非出现丢包事件,此时才减小传输速率。

TCP 拥塞控制算法(TCP congestion control algorithm) 包括三个部分:①慢启动;②拥塞避免;③快速恢复。

慢启动和拥塞避免是 TCP 的强制部分,两者的差异在于对收到的 ACK 做出反应时增加 cwnd 长度的方式。

快速恢复是推荐部分,对 TCP 非必须。

1. 慢启动

慢启动(slow-start) 状态,cwnd 的值以 1 个 MSS 开始并且每当传输报文段被确认,cwnd 就呈指数增长(乘 2)。

何时结束这种增长?

  1. 如果存在一个由超时指示的丢包事件(即拥塞),TCP 发送方将 cwnd 设置为 1 并重新开始慢启动。还将第二个状态变量值 ssthresh (”慢启动阈值 “的速记)设置为 cwnd / 2,即检测到拥塞时,将 ssthresh 设置为拥塞窗口值得一半。
  2. 当 cwnd 达到或者超过 ssthresh 的值时,继续使 cwnd 翻番可能有些鲁莽。因此,当 cwnd 的值等于 ssthresh 时,结束慢启动就并且 TCP 转移到拥塞避免模式(TCP 更为谨慎地增加 cwnd ,线性增加)。
  3. 如果检测到 3 个冗余 ACK ,这时 TCP 执行一种快速重传并进入快速恢复状态(cwnd 减半再线性增加)。

2. 拥塞避免

​ 一旦进入拥塞避免状态,cwnd 的值大约是到了上次遇到拥塞值得一半,应该使用保守的方法,每个 RTT 只将 cwnd 的值增加一个 MSS。

例如,如果 MSS 是 1460 字节并且 cwnd 是 14600 字节, 则在一个 RTT 内发送 10 个报文段。每个到达 ACK (假定每个 报文段一个 ACK)增加 1/10MSS 的拥塞窗口长度,因此在收到对所有 10 个报文段的确认后,拥塞窗口的值将增加了一个 MSS。

何时应当结束拥塞避免的线性增长(每 RTT 1 MSS)?

  • 当出现超时时,与慢启动一样,cwnd 的值设为 1 个 MSS,ssthresh 值更新为 cwnd 的一半。
  • 当收到三个冗余 ACK 事件触发时,TCP 将 cwnd 的值减半,将 ssthresh 值更新为 cwnd 的一半加三(加上已收到的 3 个冗余的 ACK 其离开网络带来的 3 个 MSS)。接下来进入快速恢复状态。

3. 快速恢复

具体来说快速恢复的主要步骤是:

  1. 当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。

  2. 再收到重复的ACK时,拥塞窗口增加1。

  3. 当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。

4. TCP 拥塞控制:回顾

忽略一条连接开始时的慢启动阶段,假定丢包由 3 个冗余的 ACK 而不是超时指示,TCP 的拥塞控制是:每个 RTT 内 cwnd 线性(加性)增加 1 MSS,然后出现 3 个冗余 ACK 事件时 cwnd 减半(乘性减)。因此,TCP 拥塞控制常常被称为 加性增、乘性减(Additive-Increase,Multiplicative-Decrease,AIMD)拥塞控制方式

5. 对 TCP 吞吐量的宏观描述

忽略开始的慢启动阶段,每次拥塞,先减半在线性增加,于是:

一条连接的平均吞吐量=0.75WRTT一条连接的平均吞吐量=\frac{0.75*W}{RTT}

3.7.1 公平性

考虑 K 条 TCP 连接,每条都有不同的端到端路径,但是都经过一段传输速率为 R bps 瓶颈链路。

所谓瓶颈链路,是指对于每条链接,沿着该连接路径上的所有其他段链路都不拥塞,而且与该瓶颈链路的传输容量相比,它们都有充足的传输容量。

如果每条连接的平均传输速率都接近 R/KR/K ,即每条连接都得到相同份额的链路带宽,则认为该拥塞控制机制是公平的。

如下图,TCP 连接在TCP 拥塞控制机制的运作下,不断线性增加拥塞窗口,然后减半,最后吞吐量大的减小,吞吐量小的增大,最终趋于平等带宽共享。

1. 公平性和 UDP

一些媒体应用使用 UDP 进行传输,占用网络资源,有可能压制 TCP 流量,现在有关于阻止 UDP 过分占用网络吞吐量的研究。

2. 公平性和并行 TCP 连接

基于 TCP 的应用使用多个并行连接占用较多份数的带宽。

3.7.2 明确拥塞通告:网络辅助拥塞控制

对于 IP 和 TCP 的扩展方案【RFC 3168】已经提出并已经实现和部署,该方案允许网络明确向 TCP 发送方和接收方发出拥塞控制信号。这种形式的网络辅助拥塞控制称为 明确拥塞通告(Explicit Congestion Notification,ECN)

如上图,当接收主机中的 TCP 通过一个接收到的数据报收到了一个 ECN 拥塞指示时,接收主机中的 TCP 通过在接收方到发送方的 TCP ACK 报文段中设置 ECE(明确拥塞通告回显)比特,通知发送主机中的 TCP 收到拥塞指示。接下来 TCP 发送方通过减半拥塞窗口对一个具有 ECE 拥塞指示的 ACK 作出反应,就像它对丢失报文段使用快速重传反应一样,并在下一个传输的 TCP 发送方到接收方的报文段首部中队 CWR(拥塞窗口缩减)比特进行设置。

附加一些缩写解释

感谢颜🐂。