TCP流量控制
流量控制平衡了生产者产生数据的速度和消费者消费数据的速度。TCP把流量控制从差错控制中独立出来。下图所示为发送方和接收方之间的单向数据传送。双向数据传送过程可以从单向传送中推断出来。
图中描绘的数据走向是从发送进程向下发送TCP,再从发送TCP到接收TCP,然后从接收TCP向上到达数据接收进程(路径1、2、和3)。不过,流量控制的反馈走向是从接收TCP到发送TCP,以及从发送TCP向上到发送进程(路径4和5)。TCP的绝大多数实现都不提供从接收进程到接收TCP的流量控制 反馈,而是接收进程准备就绪时去接收TCP那里拉取数据。换言之,接收TCP控制了发送TCP,而发送TCP控制了发送进程。
从发送TCP到发送进程的流量控制反馈(路径5)的实现很简单,一旦发送TCP的窗口慢了,就拒绝接收数据。也就是说,我们对流量控制的讨论主要集中在从接收TCP到发送TCP的反馈(路径4)。
打开和关闭窗口
为了实现流量控制,TCP强制发送方和接收方不断调整他们的窗口大小,即使对方的缓存大小在连接建立时就被定下来了。当有更多字节从发送方传来时,接收窗口关闭(其左壁向右移动),而当更多的字节被进程拉取走时,接收窗口打开(其右壁向右移动)。假设接收窗口是不会收缩的(其右壁不会向左移动)。
发送窗口的打开,关闭和收缩时被接收方控制的。当一个新的确认到达,且在这个确认允许是,发送窗口会关闭(其左壁向右移动)。当由接收方通告的接收窗口大小(rwnd)允许是,发送窗口会打开(其右壁向右移动)。发送窗口偶尔根据情况会收缩,我们假设这种情况不会发生。
一种假设情况
我们要描绘的是在连接建立阶段是如何设置发送窗口和接收窗口,并且在数据传送时,他们的状态又会发生怎样的改变。下图描绘了一个单向数据传送(从客户到服务器)的简单例子。就目前而言,让我们先忽略差错控制,假设没有报文损坏,丢失、重复或失序到达。请注意,因为是单向数据传送,我们在这里显示了两个窗口。
在客户和服务器之间一共交换了8个报文段。
1、第一个报文段从客户端到服务器(SYN报文段)以请求连接。客户在这个报文段中宣布了它的初始序号为100。当这个报文段到达服务器时,服务器分配了一个大小为800字节(假设)的缓存,并设置它的窗口覆盖整个缓存(rwnd=800)。请注意,下一个字节的编号为101.
2、第二个报文段是从服务器到客户。这是一个ACK+SYN报文段。这个报文段使用了ackNo=101来表示它希望接收编号从101开始的字节。同时它也宣布了客户可以将窗口大小设置为800字节。
3、第三个报文段是从客户到服务器的ACK报文段。
4、当客户根据服务器的指示设置自己的窗口大小(800)之后,进程又推送了200字节的数据。TCP客户用101~300给这些字节编号,然后创建一个报文段并将其发送至服务器。这个报文段显示的起始字节编号为101,并且携带了200字节的数据。然后通过调整客户的窗口来表示有200字节的数据已送达,且正在等待确认。当这个报文段到达服务器后,这些字节被保存下来,接收窗口 关闭以表示下一个希望接收的是字节301.被保存的字节占据了缓存中的200个字节。
5、第五个报文段是从服务到客户的反馈。服务器确认了编号300及其之前的所有字节(希望接收字节301)。这个报文段同时还携带了一个减小之后的接收窗口大小(600)。客户在收到这个报文段 后,将已被确认的数据从它的窗口中清除出去,并关闭窗口,以表示下一个要发送的是字节301.但是这个窗口大小要减小至600.虽然分配的缓存能够放得下800自己,但是窗口不能打开(将其右壁向右移动),因为接收方没有允许它这样做。
6、当客户进程又推送来另外300字节后,客户端TCP发送报文段6.这个报文段的序列号是301,并且包含了 300个字节。当这个报文段到达服务器后,服务器将他们保存起来,但是服务器必须减小它的窗口大小。在如武器进程拉取了100个字节的数据之后,窗口从左边关闭300个字节的量,同时又向右边打开了100个字节的量。结果,窗口大小只减小了200字节,现在接收窗口的大小是400字节。
7、在第七个报文段中,服务器对收到的字节进行确认,并宣布自己的窗口大小是400.当这个报文段到达客户后,客户没有其他选择,只能再一次减小它的窗口。客户TCP将窗口大小设置为服务器通告的rwnd=400.它的发送窗口从左边关闭300个字节,并在右边打开100个字节。
8、报文段8是拂去其进程又一次拉取了200字节之后从服务器发出的。服务器的窗口大小增加了,现在新的rwnd值是600.这个报文段告诉客户说,服务器仍然在期盼着字节601,但是服务器窗口大小已经扩展到600了。有一点我们需要说明,这个报文段的发送与否取决于具体实现的策略。有些实现可能不允许在这种情况下通告rwnd的值,服务器必须在接收到一些数据之后才被允许做这件事情。当这个报文段到达客户后,客户讲发送窗口再打开200字节,并且没有做任何关闭动作。结果就是客户的窗口大小增加到600字节。
窗口的收缩
正如我们前面提到的,接收窗口不允许收缩。但是,如果接收方指定的rwnd值会导致发送窗口的缩小,那么发送窗口就收缩。某些实现是不允许发送窗口收缩的。它限制了发送窗口的右壁不允许向左移动。换言之,为了防止发送窗口的收缩,接收方在最近的和新的确认以及最近的和新的rwnd值之间始终要满足如下关系。
新的ackNo + 新的rwnd >= 最近的ackNo + 最近的rwnd
这个不等式的左侧代表了窗口右壁的新位置,右侧则代表了窗口右壁原来的位置(相对于序号空间)。这个关系说明右臂永远不会向左移动。接收方必须按照这个不等式来检查它的通告是否合法。不过,请注意这个不等式仅当S(f) < S(n)时才有效,我们应当紧急所有的计算都是模2(32)的。
下图说明了为什么有些实现强制要求遵守以上不等式的原因,下图a部分显示了最近收到的一个确认及其rwnd的值。图中b部分则显示了发送方发送了字节206~214之后的窗台。字节206~209已经被清除出缓存。但是,新的通告指定的rwnd的值是4,这样的话210+4<206+12.当发送窗口收缩时就产生了一个问题:已经发送出去的字节214落在了窗口之外。前面所讨论的那个关系式将迫使接收方让窗口的右壁维持在(a)图部分中所显示的位置上,因为接收方并不知道从字节210~217中有哪些已经发送出来了。要防止这种状况发生的在一个办法是让接收方推迟发送反馈,直至它的窗口有足够的缓存空间可用。换言之,接收方应当等待,直至它的进程消耗了更多的字节,以满足上面给出的不等式。
窗口关闭
使发送窗口的右壁向左移动以收缩发送窗口是非常不希望出现的。但是有一个例外:接收端可以用发送rwnd为0的报文段来暂时关闭窗口。这种情况的发生是由于接收方某种原因在一段时间内不愿意从发送发接收任何数据。此时,发送方实际上并非真正地把窗口大小收缩了,而只是暂停发送数据,直至收到一个新的通告位置。我们在后面将会看到,即使在接收方的命令下关闭了窗口,发送方仍然可以发送具有一个字节数据的报文段,称为探测,用于防止死锁。
糊涂窗口综合征(silly window syndrome)
在滑动窗口的操作中可能会出现一个严重的问题,这就是发送应用程序产生数据的速度很慢,或者接受应用程序消费数据的速度很慢,或者两者都有。不管是上述情况中的哪一种,都会使得发送数据的报恩段很小,这就会降低运行的效率。例如,假设TCP发送的报文段只包含1个字节的数据,那么意味着我们为传送1个字节的数据而发送了41个字节的数据报(20字节TCP首部和20字节IP首部)。此时的额外开销达到了41/1,这就表示我们使用网络容量的效率非常低。再算上数据链路层和物理层的额外开销后,这种低效率的程度就更加严重了。这个问题成为糊涂窗口综合征。我们先来分别描述一下连接两端的问题格式怎样产生的,然后再给出解决问题的建议。
发送方产生的症状
如果发送TCP正在为一个产生数据速度很慢的应用程序服务,例如一次产生1个字节,那么就有可能产生糊涂窗口综合征。这个应用程序一次把1个字节数据写入发送TCP的缓存。如果发送TCP没有任何特殊的指令,它就会产生只包括1个字节数据的报文段。其结果就是很多个41字节的报文段再互联网中传来传去。
解决这个问题的方法是防止发送TCP一个字节一个字节的发送数据。必须要强迫发送TCP等待,并把数据收集成较大的数据块后再发送。发送TCP需要等待多长时间呢?如果等待的过长,就会延迟了整个处理过程。如果它等待的时间不够长,最后很有可能还是发送了一个个小报文段。Nagle找到了一个很精巧的解决方法。
Nagle算法
1、发送TCP把它从应用程序收到的第一块数据发送出去,哪怕只有1个字节。
2、在发送了第一个报文段后,发送TCP就在输出缓存中累计额数据并等待,直至收到TCP发来的确认,或者已积累了足够的数据可以装成最大长度的报文段。这是,发送TCP就可以发送这个报文段了。
3、对剩下的传输,不断重复步骤2.如果收到了对报文段2的确认,或者已积累到足够的数据可以装成最大长度的报文段,报文段3就必须发送出去。
Nagle算法的巧妙之处在于他的简单,实际上它权衡了应用程序产生数据的速率和网络运输的速率。若应用程序比网络更快,则报文段就比较大(最大长度报文段)。若应用程序比网络慢,则报文段就较小(小于最大长度报文段)。
接收方产生的症状
如果接收TCP为一个消耗数据很慢的应用程序服务,比如,一次消耗1个字节,它就有可能产生糊涂黄口综合征。假定发送应用程序产生1K字节块的数据,但接收应用程序每次只能消耗1个字节的数据。再假定接收TCP的输入缓存为4K字节。发送方先发送了4K字节的数据。接收方就将其存储在缓存中。现在缓存满了。接收方通告的窗口值为零,这表示发送方必须停止发送数据。接收应用程序从接收TCP的输入缓存中读取第一个字节的数据。现在输入缓存中有了1字节的空间。接收TCP宣布其窗口值为1个字节,于是正在渴望发送数据的发送TCP会把这个宣布当做好消息,并发送一个只包含1个字节数据的报文段。这样的过程将持续下去。1个字节的数据被消耗掉,然后发送携带1个字节数据的报文段。我们又一次遇到了效率问题和糊涂窗口综合征。
Clark解决方法
Clark解决方法是只要有数据到达就发送确认,但在缓存中有足够大的空间放入最大长度的报文段之前,或者至少有一半的缓存空间为空之前,一直宣布窗口大小为零。
推迟确认
第二种解决方法是推迟发送确认。这就表示当报文段到达时并不立即发送确认。接收方在对接收到的报文段进行确认之前一直等待,直至输入缓存有足够的空间位置。推迟发送确认防止了发送TCP滑动它的窗口。发送TCP在发送了数据之后就停下来,因而防止了这种症状的出现。
推迟确认还有另一个优点:它减少了通信量。接收方不需要对每一个报文段进行确认。但它也有一个缺点,就是确认的推迟有可能迫使发送方重传未被确认的报文段。TCP对这个优点和缺点进行了平衡。目前它的定义是推迟确认不能超过500ms.
上一篇: Linux内核同步