22.1简介
我们已经知道接收者会在窗口大小中向发送者通告一个适当的数据数量,TCP通过这种方法来进行流量控制。当窗口大小变成0时将会发生什么情况?它将有效地阻止发送者向另一端传输数据,直到窗口大小变成非零。
我们可以在图20.3看到这种情况。段9打开了被段8关闭的窗口,当发送者收到段9以后,它立即开始发送数据。TCP必须能够处理打开窗口的确认(段9)丢失的情况。确认不是可靠传输的,也就是说,TCP不会对确认进行ACK,它只ACK包含数据的段。
附图20.3
如果有确认丢失,我们要结束两端的互相等待:接收者等待接收数据(因为它提供给发送者一个非零的窗口),发送者则在等待允许它发送的窗口更新(已丢失!)。为了防止这种情况的死锁发生,发送者使用了一个持续计时器(persiet timer)来周期性的询问接收者是否已增加了窗口。从发送者发出的这些段称为窗口探测(window probes)。在本章,我们研究窗口探测和持续定时器。我们同样检查和持续定时器相关的糊涂窗口综合症(silly window syndrome)。
22.2 范例
为了解工作中的持续定时器,我们将启动一个从客户端监听连接请求的接收者进程,接受连接请求,并且在从网络读取数据之前睡眠(sleep)很长一段时间。
Sock程序的暂停选项-P可以使服务器在接受连接请求和执行第一次读之间睡眠。我们用如下方式运转服务器:
svr4 % sock -i -s -P100000 5555
这样可以让服务器在从网络读取数据之前睡眠100000秒(27.8个小时)。客户端运行在主机bsdi,并向服务器的5555端口写1024字节的数据。图22.1显示了tcpdump输出(我们已经在输出中去掉了连接的建立过程)
图22.1持续定时器探测零大小窗口的例子
段1-13显示了从客户端到服务器的正常数据传输,用9216个字节的数据填充了窗口。服务器通告的窗口大小是4096,并有一个默认的socket缓冲区,大小也是4096。这是SVR4中TCP/IP代码和数据流子系统(stream subsystem)之间交互的某种形式。(This is some form of interaction between the TCP/IP code and the streams subsystems in SVR4.)
在段13,服务器确认了前面4个数据段,当通告窗口大小是0,阻止了客户端传输更多的数据。这将引起客户端设置它的持续定时器。当定时器期满时,如果客户端未收到窗口更新,它就探测这个空窗口,看是否有窗口更新丢失了。这是因为我们的服务器正在睡眠,那9216个字节被TCP放在缓冲区里,正等待着应用程序对它们的读取。
注意客户端探测窗口间的时间间隔。第一个(段14)是在收到0大小窗口的4.949秒之后。下一个(段16)是4.996秒之后。往下的前后两段的间隔大约是6,12,24,48和60秒。
为什么这些间隔总是比5,6,12,24,48和60少零点几秒?这些探测是由TCP的500毫秒定时器期满触发的。当定时器期满时,窗口探测被发出,并且在4毫秒之后收到回复。收到回复引起定时器的重新启动,但到下一个时钟tick的时间大约是(500-4)毫秒。(?)
使用标准的TCP指数后退(exponential backoff)来计算持续定时器Exponential 。对于一个典型的LAN,第一次超时的计算结果是1.5秒。第二次超时值是第一次的结果乘以2,即3秒。下一次乘以4,得到6,再往下乘以8,得到12……但是持续定时器总是在5到60秒之间,这就解释了我们在图22.1中的所见。
窗口探测包含了一个字节的数据(顺序号9217)。TCP总是允许在已关闭窗口的结尾之外发送一个字节的数据。注意,尽管这样,但返回的告知窗口大小为0的确认并不ACK这个字节。(它们只ACK9216之前(包括9216)的字节。)因此该字节可以被持续的重传。
持续状态的特征和21章重传超时的不同在于TCP从不会放弃发送窗口探测。这些窗口探测以60秒的时间间隔连续发送,直到窗口打开,或者连接被关闭。
22.3 糊涂窗口综合症(silly window syndrome)
TCP使用的这种基于窗口的流量控制机制,可能导致进入一种叫做糊涂窗口综合症(SWS)的条件。当它发生时,小的数据通过连接被交换,而全长(full-sized)段却无法传输 [Clark1982] 。
连接两端都可能引起这种情况:接收者通告小的窗口(而不是等待出现较大窗口再通告),发送端发送小的数据(而不是等待其它的数据来发送一个较大的段)。可以在两端采取正确的措施来避免糊涂窗口综合症的发生。
1. 接收者不可以通告小窗口。通常的算法是,接收者不通告比当前通告(可能是0)大的窗口,除非窗口可以增加一个全长段(比如正在被接收的MSS),或者增加半个接收者缓冲区空间,其它情况都要小于当前通告。(即等到窗口到一定大小后再通告。)
2. 发送者通过停止发送来避免糊涂窗口综合症,除非下列某个条件成立:(a)一个全长段能够被发送,(b)至少有对方曾经通告的最大窗口一半的段能够被发送,(c)不需要确认(即没有未被确认的数据)或者连接上Nagel算法(19.4)已被禁止时,任何数据都可以被发送。
条件(b)用以处理总是通告小窗口(可能比段长度更小)的情况,条件(c)阻止我们在有未被确认的数据(正在等待被确认),以及Nagel算法被禁止时发送小段。如果应用程序正在写小数据(比如比段长度更小),条件(c)可以避免糊涂窗口综合症。
这3个条件也需要我们回答这样一个问题:当有未被确认的数据时,如果有Nagel算法阻止我们发送小的段,那么到底多小才算小?从条件(a)我们知道“小”意思是字节数少于段长度。条件(b)只用在较老的原始主机。
步骤2中的条件(b)需要我们跟踪由另一端通告的最大窗口长度。这是发送者对对方接收缓冲区的大小的猜测尝试。尽管接收者缓冲区在连接建立时可能减小,但事实上这很少
发生。
范例*
在发送主机sun使用sock程序向网络写6个1024字节的数据。
sun % sock -i -n6 bsdi 7777
在主机bsdi的接收进程放置一些暂停,在第一次读之前暂停4秒,在连续两次读之间暂停2秒。而且接收者每次读256字节
bsdi % sock -i -s -P4 -p2 -r256 7777
初始的暂停是为了填满接收者的缓冲区,迫使发送者停止发送。由于接收者接着从网络读了少量数据,我们期望能看到接收者执行的糊涂窗口综合症的避免措施。
图22.2是传输6144个字节数据的时间线。
图22.2 显示接收者避免糊涂窗口综合症的时间线
我们同样需要跟踪读取数据的应用程序在每个时间点的变化,接收缓冲区的当前字节数,以及缓冲区可用空间的字节数。图22.3显示了这些变化。
图22.3接收者避免糊涂窗口综合症的事件顺序
图22.3 第一列是每个动作发生的相对时间点。那些带三位小数点的是从tcpdump输出得到的(图22.2)。那些小数点后为99的是接收端主机发生行为时的假想(assumed)时间。
当从发送者收到数据时,接收者的缓冲区的数据数量增加;而当应用程序从缓冲区读取数据时,数量减少。我们需要跟踪的是接收者发给发送者的窗口通告,从中我们可以看到接收者避免糊涂窗口综合症的方法。
前四个数据段和相关的ACK(段1-5)显示发送者填满了接收者缓冲区。发送者被迫停止,但还有数据需要发送。它设置持续定时器的最小值为5秒。
当持续定时器期满时,一个字节的数据被发送(段6)。由于接收端应用程序已经从缓冲区读取了256字节(在时间3.99),因此该字节被接收并确认(段7)。但通告的窗口仍是0,这是由于接收者还没有腾出能够容难一个全长段或半个缓冲区大小的空间。这是接收端的糊涂窗口综合症的避免措施。
发送者的持续定时器被重置,并在5秒之后(在时间10.151)期满,又有一个字节发送并被确认(段8,9)。这时接收者缓冲区的可用空间是1022字节,因此仍通告0窗口。
在时间15.151,定时器期满,段10和11被发送和接收。此时可用空间为1533字节,(大于全长段1024)因此通告一个非零窗口。发送者立即使用这个窗口,发送1024个字节(段12)。对这1024个字节的确认(段13)通告窗口大小是509字节。这似乎与我们前面看到的小窗口通告相矛盾(即为什么通知窗口大小不是0)。
这里段11通告一个1533字节的窗口,并且发送者只发送1024字节。如果段13通告的窗口大小是0,就与窗口不能通过左移右边缘来收缩的TCP原则(20.3)相冲突。这就是为何通告509字节小窗口的原因。
接下来我们看,发送者没有立即向小窗口发送数据。这是发送者糊涂窗口综合症的避免方法。相反地,它又等待了一个持续定时器时间(到时间20.151),发送了509字节数据。尽管它最终发送这509个字节的小数据,但它在发送前等待了5秒,看是否有ACK到达以使窗口打开得更大些。这509个字节使得接收者缓冲区的可用空间剩下768字节,(不到一个全长度)因此确认(段15)通告窗口大小是0。
在时间25.151,持续定时器期满,发送者发送一个字节的数据(段16)。在段17接收者通告窗口大小1279字节。
发送者有额外511个字节需要发送(上次发送遗留下的),因此它在收到段17后立即发送这511个字节(段18)。该段也包括了一个FIN标志,段19对数据和FIN进行了确认,通告窗口为767字节。(1279-511=768,为什么通告窗口少了一个字节?被FIN消费了)
发送端应用程序在写完第六段1024字节的数据后发出了close,它由ESTABLISHED状态,迁移到FIN_WAIT_1状态,再到FIN_WAIT_2状态(图18.12)。它保持这个状态,直到从另一端收到FIN。FIN_WAIT_2这个状态没有定时器,因为她在段18中发送的FIN已经由段19确认。这就是我们在它收到对方的FIN(段21)之前看不到其他传输的原因。
接收端的应用程序继续每隔两秒地从缓冲区读取256个字节的数据。为什么在时间39.99,段10被发送?当应用程序读到39.99时,接收者缓冲区空间已经由上次通告的767字节(段19)上升到了2816。缓冲区增加的空间是2049空间。前面我们提到,接收者缓冲区空间增加它的一半时,需要发送一个窗口更新。从中我们可以发现,应用程序从缓冲区读取数据时,接收端在时时地检查是否该发送一个窗口更新。
在时间51.99,应用程序执行最后一次读取,并收到一个文件结束标志,因为缓冲区已经为空。最后两个段被发送,连接终止。