引言
TCP通过让接收方指明希望从发送方接收的数据字节数(即窗口大小)来进行流量控制。如果窗口大小为 0会发生什么情况呢?这将有效地阻止发送方传送数据,直到窗口变为非0为止。
TCP不对ACK报文段进行确认, TCP只确认那些包含有数据的ACK报文段。
如果一个确认丢失了,则双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非 0的窗口),而发送方在等待允许它继续发送数据的窗口更新。为防止这种死锁情况的发生,发送方使用一个坚持定时器 (persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查 (window probe)。
一个例子
为了观察到实际中的坚持定时器,我们启动一个接收进程。它监听来自客户的连接请求,接受该连接请求,然后在从网上读取数据前休眠很长一段时间。
sock程序可以通过指定一个暂停选项 - P使服务器在接受连接和进行第一次读动作之间进入休眠。我们以这种方式调用服务器:
svr4 % sock -i -s -P100000 5555
该命令在从网络上读数据之前休眠 100 000秒(27.8小时)。客户运行在主机bsdi上,并向服务器的5555端口执行1024字节的写操作。下图给出了tcpdump的输出结果(我们已经在结果中去掉了连接的建立过程)。
报文段1 ~ 1 3显示的是从客户到服务器的正常的数据传输过程,有9216个字节的数据填充了窗口。服务器通告窗口大小为4096字节,且默认的插口缓存大小为4096字节。但实际上它一共接收了9216字节的数据,这是在S V R 4中TCP代码和流子系统(stream subsystem)之间某种形式交互的结果。
在报文段1 3中,服务器确认了前面 4个数据报文段,然后通告窗口为 0,从而使客户停止发送任何其他的数据。这就引起客户设置其坚持定时器。如果在该定时器时间到时客户还没有接收到一个窗口更新,它就探查这个空的窗口以决定窗口更新是否丢失。由于服务器进程处于休眠状态,所以TCP缓存9216字节的数据并等待应用进程读取。
请注意客户发出的窗口探查之间的时间间隔。在收到一个大小为 0的窗口通告后的第1个(报文段1 4)间隔为4.949秒,下一个(报文段16)间隔是4.996秒,随后的间隔分别约为6,12,24,48和60秒。
为什么这些间隔总是比5、6、12、24、48和60小一个零点几秒呢?因为这些探查被TCP的500 ms定时器超时例程所触发。当定时器时间到时,就发送窗口探查,并大约在4 ms之后收到一个应答。
接收到应答使得定时器被重新启动,但到下一个时钟滴答之间的时间则约为500减4ms。计算坚持定时器时使用了普通的 TCP指数退避。对一个典型的局域网连接,首次超时时间算出来是1 . 5秒,第2次的超时值增加一倍,为 3秒,再下次乘以4为6秒,之后再乘以8为1 2秒等。但是坚持定时器总是在5 ~ 6 0秒之间,这与我们在上图中观察到的现象一致。
窗口探查包含一个字节的数据(序号为 9 2 1 7)。TCP总是允许在关闭连接前发送一个字节的数据。请注意,尽管如此,所返回的窗口为0的ACK并不是确认该字节(它们确认了包括9216在内的所有数据),因此这个字节被持续重传。
坚持状态与重传超时之间一个不同的特点就是 TCP从不放弃发送窗口探查。这些探查每隔60秒发送一次,这个过程将持续到或者窗口被打开,或者应用进程使用的连接被终止。
本文转自 《TCP/IP详解》卷1