《TCP/IP详解 卷1》 笔记: TCP的成块数据流

引言

    本节我们举一些例子说明TCP的成块数据流并介绍TCP所使用的被称为滑动窗口协议的流量控制方法。

正常数据流

    当我们从主机svr4向主机bsdi发送8192字节数据(分8次发送,每次发送1024字节)时,可能会产生如下时序的数据流:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    建立TCP连接后,svr4发送了3个1024字节数据的报文段,然后收到bsdi发来的两个ACK。这两个ACK报文段分别确认了2048字节和1024字节数据,它们是这样产生的:当一个分组到达时,它首先被设备中断例程进行处理,然后放置到IP的输入队列中。三个报文段4、5和6依次到达并按接收顺序放到IP的输入队列。IP将按同样顺序将它们交给TCP。当TCP处理报文段4时,该连接被标记为产生一个经受时延的确认。TCP处理下一报文段(5),由于TCP现在有两个报文段需要确认,因此产生一个序号为2048的ACK(报文段7),并清除该连接产生经受时延的确认标志。TCP处理下一个报文段(6),而连接又被标志为产生一个经受时延的确认。在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为3073的ACK(报文段8)。

    上图中我们要注意以下两点:

    1. 报文段7、14和16中的ACK确认了两个收到的报文段。使用TCP的滑动窗口协议时,接收方不必确认每一个收到的分组。在TCP中,ACK是累积的—它们表示接收方已经收到了一直到确认序号减1的所有字节。
    2. 报文段8的窗口大小为3072。这说明接收方的缓冲区还有1024字节数据等待应用层读取。
    用tcpdump看到的是TCP动态活动的情况。我们在线路上看到的分组顺序依赖于许多无法控制的因素:发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于操作系统的调度)和网络的动态性(如以太网的冲突和退避等)。对这两个TCP而言,没有一种单一的、正确的方法来交换给定数量的数据。
快的发送方和慢的接收方

    下图是从一个快的发送方发送8092字节数据到一个慢的接收方产生的数据流:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    发送方发送4个1024字节数据的报文段去填充接收方的缓冲区,然后等待一个ACK。接收方发送ACK(报文段8),但通告其窗口大小为0,这说明接收方已收到所有数据,但这些数据都在接收方的TCP缓冲区,因为应用程序还没有机会读取这些数据。另一个ACK在17.4ms后发送,表明接收方现在可以接收另外的4096个字节的数据。虽然这看起来像一个ACK,但由于它并不确认任何新数据,只是用来增加窗口的右边沿,因此被称为窗口更新

滑动窗口

    下图用可视化的方法显示了我们上文观察到的滑动窗口协议:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    待发送的数据字节从1至11标号,这些数据分为如下四部分:

        1. 已经发送并被确认(1~3)。

        2. 已经发送但未被确认(4~6)。

        3. 能够发送(7~9)。

        4. 不能够发送(10~11)。

    接收方通告的窗口被称为提供的窗口,它覆盖4~9字节的区域(窗口大小为6)。发送方计算它的可用窗口(7~9),该窗口表明多少数据可以立即被发送。我们使用三个术语来描述窗口左右边沿的运动:

        1. 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在发送的数据被确认时。
        2. 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端TCP接收缓冲区中的数据已被进程读取时。
        3. 当右边沿向左移动时,我们称之为窗口收缩。RFC1122强烈建议不要使用这种方式。

    如果左边沿到达右边沿,则称其为一个零窗口,此时发送方不能够发送任何数据。

一个例子

    下图说明了第一个图中所示的数据传输过程中滑动窗口协议的动态性:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    以该图为例可以总结如下几点:

        1. 发送方不必发送一个全窗口大小的数据。

        2. 来自接收方的一个报文段确认数据并把窗口向右边滑动。这是因为窗口的大小是相对于确认序号的

        3. 正如从报文段7到报文段8中变化的那样,窗口的大小可以减小,但是窗口的右边沿却不能够向左移动。
        4. 接收方在发送一个ACK前不必等待窗口被填满。

窗口大小

    由接收方提供的窗口的大小和接收缓冲区大小相关,这会影响TCP的性能。缓冲区(接收和发送)大小可以由接收进程设置。在4.3BSD中,发送方和接收方的默认缓冲区大小为4096个字节(本书中的大都数例子都是这样)。4.4BSD中则使用更大的数值,如8192或16384。一份研究表明:对以太网而言,默认的4096字节并不是最理想的缓冲区大小,将大小增加到16384个字节可以增加约40%左右的吞吐量。

    下图是一个接收方使用6144字节大小的缓冲区的例子:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    注意的是14、15和16这三个连续的窗口更新报文段。许多TCP实现在窗口大小增加了两个最大报文段长度(本例中为2048字节,因为MSS为1024字节)或者最大可能窗口的50%(本例中为2048字节,因为最大窗口大小为4096字节)时发送窗口更新。

PUSH标志

    之前的TCP例子中我们都看到了PUSH标志,但一直没有介绍它的使用。发送方使用PUSH标志通知接收方将接收缓冲区中所有的数据提交给接收进程。

    如果待发送数据将清空发送缓存, 则大多数的源于伯克利的实现能够自动设置 PUSH标志。比如图20-1中我们观察到所有的8个数据报文段(4~6、9、11~13和15)都设置了PUSH标志。
慢启动
    迄今为止,在本章所有的例子中,发送方一开始便向网络发送多个报文段,直至达到接收方通告的窗口大小为止。当发送方和接收方处于同一个局域网时,这种方式是可以的。但是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,就有可能出现一些问题。为解决这些问题,TCP使用一种被称为“慢启动(slow start)”的算法。该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。
    慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。

成块数据的吞吐量

    下图显示了左边的发送方和右边的接收方之间的一个TCP连接上的时间系列:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    在时间0,发送方发送了一个报文段。由于发送方处于慢启动中(其拥塞窗口为1个报文段),因此在继续发送以前它必须等待该数据段的确认。在时间1, 23,报文段从左向右移动一个时间单元。在时间4接收方读取这个报文段并产生确认。经过时间567ACK移动到左边的发送方。 于是我们有了一个8个时间单元的往返时间(Round-TripTime,RTT)。

    当发送方收到ACK后,在时间8和9发送两个报文段(我们标记为2和3)。此时它的拥塞窗口为2个报文段。这两个报文段向右传送到接收方,在时间12和13接收方产生两个ACK。

    (通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设备的等待时间等引起)和一个取决于媒介速率(即媒介每秒可传输的比特数)的发送时延。对于一个给定的两个接点之间的通路,传播时延一般是固定的,而发送时延则取决于分组的大小。在速率较慢的情况下发送时延起主要作用,而在千兆比特速率下传播时延则占主要地位。)

    下图是后面16个时间单元:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    2个ACK的到达使得拥塞窗口从2个报文段增加为4个,而这4个报文段在时间16~19时被发送。第1个ACK在时间23到达。4个ACK的到达使得拥塞窗口从4个报文段增加为8个,并在时间24~31发送8个报文段。

    在时间31及其后续时间,发送方和接收方之间的管道被填满。此时不论拥塞窗口和通告窗口是多少,它都不能再容纳更多的数据。每当接收方在某一个时间单位从网络上移去一个报文段,发送方就再发送一个报文段到网络上。但是不管有多少报文段填充了这个管道,返回路径上总是具有相同数目的ACK。这就是连接的理想稳定状态

带宽时延乘积
    现在来回答窗口应该设置为多大的问题。 在我们的例子中,作为最大的吞吐量,发送方在任何时候有8个已发送的报文段未被确认。接收方的通告窗口必须不小于这个数目,因为通告窗口限制了发送方能够发送的段的数目。
    可以计算通道的容量为:
        capacity(bit)=bandwidth(b/s)×round-trip time(s)
    一般称之为带宽时延乘积。这个值依赖于网络速度和两端的RTT,可以有很大的变动。例如,一条穿越美国(RTT约为60ms)的T1的电话线路(1544000b/s)的带宽时延乘积为11580字节。不论是带宽还是时延均会影响发送方和接收方之间通路的容量。
拥塞

    当数据到达一个大的管道(如一个快速局域网)并向一个较小的管道(如一个较慢的广域网)发送时便会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。下图显示了一个典型的大管道向小管道发送报文的情况:

《TCP/IP详解 卷1》 笔记: TCP的成块数据流

    之所以说它典型,是因为大多数的主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连(我们再次假定图中上半部分的报文段(9~20)都是相同的,而图中下半部分的ACK也都是相同的)。

    在该图中,我们已经标记路由器 R 1为“瓶颈”,因为它是拥塞发生的地方。它从左侧速率较高的局域网接收数据并向右侧速率较低的广域网发送。 当路由器R2将所接收到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上同样的间隔,尽管局域网具有更高的带宽。类似地,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔一致。
    上图中已经假定发送方不使用慢启动,它按照局域网的带宽尽可能快地发送编号为1~20的报文段(假定接收方的通告窗口至少为20个报文段)。正如我们看到的那样,ACK之间的间隔与在最慢链路上的一致。假定瓶颈路由器具有足够的容纳这20个分组的缓存。如果这个不能保证,就会引起路由器丢弃分组。下节我们讨论TCP如何使用拥塞避免算法避免这种情况。
紧急方式
    TCP提供了“紧急方式(urgent mode)”,它使一端可以告诉另一端有些具有某种方式的“紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。TCP首部中的URG标志位被置1,并且一个16位的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。
    Telnet和Rlogin常使用紧急方式。它们使用紧急方式是因为从客户到服务器方向上的数据流很可能要被客户的TCP停止(也即,它通告了一个大小为0的窗口)。但是如果服务器进程进入了紧急方式,尽管它不能够发送任何数据,服务器TCP也会立即发送紧急指针和URG标志。当客户TCP接收到这个通知时就会通知客户进程,于是客户可以从服务器读取其输入、打开窗口并使数据流动。