TCP的流量控制

下面简单介绍了TCP的流量控制。

流量控制是发送方和接收方合作完成的端到端的流量控制,用于防止发送方发送数据包的速率超过了接收方的接收速率,造成了接收方溢出。

流量控制早于拥塞控制出现,是实现拥塞控制的基础,因此在深入学习拥塞控制之前,有必要回顾流量控制。

 

1.1   Acknowledgement机制

TCP基于ACK实现可靠传输。sender每发送一个packet,都会等待receiver的响应,而receiver收到packet之后,会立即反馈一个ACK,ACK表明receiver成功收到了该packet[5]

ACK机制得以稳定运行的前提之一是所有的数据都是按序编号的,因此packet和其ACK便有了对应的依据。实际上,TCP不但将所有数据依次编号,而且精确到字节。在sender发送的每个packet的头部信息中,都写明了其序号(sequence number),同时,receiver在每个ACK中,都写明了所期望的下一个packet的序号,sender再基于该序号发送接下来的packet。ACK机制使得TCP得以自我运转,而不依赖外部因素,因此在一些文献中,Acknowledgement机制又称为Self-Clocking机制。

基于Acknowledgement机制,产生了“数据包守恒准则”[6]。根据该准则,如果一个网络上稳定地传输着满额的数据包,就认为它是平衡的。一个平衡的网络上,只有在有数据包离开时,才会有新的数据包加入。因此,平衡的网络可以避免拥塞的发生。

从sender发送packet起,到收到相应的ACK止,这个时间间隔称为一个RTT(roung trip time),该时间是在TCP运行的过程中,通过实时统计和估算得到的,并且是动态改变的[1]

1.2   Cumulative Acknowledgement

在非SACK[17]的TCP中,receiver采取累积确认的模式[2]。在发送ACK时,总是回复这样一个序号,该序号之前的所有packet都已被receiver接收,与它相邻的下一个packet没有被receiver接收。即累积回复从左到右最大的一块连续区域。累积确认在所到达的packet填补了receiver缓冲区的空洞时,会被体现出来。

1.3   Sliding Window机制

sender和receiver都维持各自的window,分别称为发送窗口和接收窗口。发送窗口(send window)规定了可发送的范围,sender只能按序地发送该窗口内的packet。接收窗口(receive window)则相当于receiver的可用缓冲区(不一定相等,但有直接关系),用于接收来自sender的packet。另外,发送窗口又分为前后两部分:

[SND.UNA, SND.NXT)之间的部分,此窗口内的packet已经被发送出去,但尚未收到ACK,RFC1122称这些packet为outstanding的,即还在网络中传输的。

[SND.NXT, SND.UNA+SND.WND]之间的部分,称为可用窗口(usable window),此窗口内的packet尚未被发送出去。

其中,SND.UNA表示尚未收到ACK的packet中最靠左的一个,也是发送窗口的起点。

SND.NXT表示即将发送的下一个packet的序号。

SND.WND表示发送窗口的大小。

可以看出,随着sender不断地接受ACK,SND.UNA的值会递增,发送窗口就会不断地向右滑动,这就是滑动窗口机制。

窗口的概念是Flow Control的基础。Flow Control协调双方的速率,实际上就是协调双方窗口大小。实现窗口协调的前提是,至少有一方能够获知对方窗口的大小。为此,在每个ACK中,都会写明receiver的接收窗口的大小(称为offered window),使得sender可以依此调节发送窗口的大小。sender将接收窗口大小减去尚未收到ACK的数据的大小,作为可用窗口的大小,从而控制发送速率[5]

对于receiver来说,

LastByteRcvd – LastByteRead <= RcvBuffer

rwnd = RcvBuffer – [LastByteRcvd – LastByteRead]

对于sender来说,

LastByteSent – LastByteAcked <= rwnd

SND.UNA=LastByteAcked-1

SND.NXT=LastByteSent-1

SND.WND=rwnd

1.4   Silly Window Syndrome

所谓Silly Window Syndrome[5],可以用下面一个例子解释。

假设receiver最初告诉sender,接收窗口的初始值为有1000字节,那么发送窗口/可用窗口的初始值也为1000字节。再假设每个packet的大小被设置为200字节,那么第一次sender会发送5个packet。当receiver接收到第一个packet后,有两种处理方式:(1)可以将其立即提交给应用层,从而重新腾出缓冲区,此时ACK中的offered window仍然是1000字节。(2)也可以不处理,等到一定时刻,再提交给应用层,此时ACK中的offered window就是1000-200=800字节。对于第二种方式,sender在收到ACK后,计算可用窗口的大小,结果为800-800=0,即sender不能继续发送数据,直到receiver腾出更多缓冲区为止。

我们下面只考虑第一种方式,此时sender计算到的可用窗口的大小为1000-800=200,因此sender可以发送一个新的200字节的packet。实际上可以看出,sender发送数据的大小就是receiver接收数据的大小。接下来考虑一种异常,某个时刻,可用窗口的大小为200字节,但是应用层要发送的数据只有50字节并且设置了PUSH,此时sender只能将50字节的数据作为一个packet发送出去,一个RTT后,sender收到对应的ACK,重新计算可用窗口的大小为1000-950=50字节,因此sender仍然只能发送50字节的packet。可以预见,50字节的窗口会一直存在,网络中会持续出现零碎的packet,因此TCP性能会降低。

在RFC813中提出了解决方法,主要思想是当sender遇到小窗口时,推迟新packet的发送,以等待receiver腾出更大的接收窗口再发送。这种思想分别运用到sender和receiver之上,形成了两种算法。(1)在sender端,会计算可用窗口相对接收窗口的比例,当其小于一个阈值时,就推迟新packet的发送。(2)在receiver端,如果发现offered window很小,那么会进一步将ACK中的offered window的值减小,使得sender计算得到的可用窗口过小而无法发送新的packet。等腾出更大接收窗口时,再一次性通知sender,使其得以继续发送packet。

posted @ 2014-03-12 11:04  使命召唤  阅读(2031)  评论(0编辑  收藏  举报