TCP使用四种定时器(Timer,也称为“计时器”):
重传计时器:Retransmission Timer
坚持计时器:Persistent Timer
保活计时器:Keeplive Timer
时间等待计时器:Time_Wait Timer。
![250af3f03adf2053eafc4151ba36be08.png](https://i-blog.csdnimg.cn/blog_migrate/6543f68cd108cb3c48632b6b5a4a6f5b.jpeg)
(1)重传计时器:
很明显重传定时器是用来计算TCP报文段的超时重传时间的(至于超时重传时间的确定,这里涉及到一大堆的算法,书上有说,我这里不细谈了)。每发送一个报文段就会启动重传定时器,如果在定时器时间到后还没收到对该报文段的确认,就重传该报文段,并将重传定时器复位,重新计算;如果在规定时间内收到了对该报文段的确认,则撤销该报文段的重传定时器。
重传定时器:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位;
重传时间=2*RTT;
RTT的值应该动态计算。常用的公式是:RTT=previous RTT*i + (1-i)*current RTT。i的值通常取90%,即新的RTT是以前的RTT值的90%加上当前RTT值的10%.
Karn算法:对重传报文,在计算新的RTT时,不考虑重传报文的RTT。因为无法推理出:发送端所收到的确认是对上一次报文段的确认还是对重传报文段的确认。干脆不计入。
(2)坚持计时器:persistent timer
专门为对付零窗口通知而设立的。
当发送端收到零窗口的确认时,就启动坚持计时器,当坚持计时器截止期到时,发送端TCP就发送一个特殊的报文段,叫探测报文段,这个报文段只有一个字节的数据。探测报文段有序号,但序号永远不需要确认,甚至在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端TCP,确认已丢失,必须重传。
坚持计时器的截止期设置为重传时间的值,但若没有收到从接收端来的响应,则发送另一个探测报文段,并将坚持计时器的值加倍和并复位,发送端继续发送探测报文段,将坚持计时器的值加倍和复位,知道这个值增大到阈值为止(通常为60秒)。之后,发送端每隔60s就发送一个报文段,直到窗口重新打开为止;
(3)保活计时器:keeplive timer
每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(没75秒发送一个)还没收到响应,则终止连接。
服务器应用程序用来探知客户主机是否崩溃并启动,或者崩溃关机等场景。
具体来说客户主机必定处于以下4中状态之一:
- 正常运行。TCP响应正常,服务端知道客户端工作正常,服务器在两小时后将保活定时器复位,如果这两小时之间有应用程序通过这个连接通信,保活定时器在交换数据后的未来两小时再复位;此时的服务器应用程序不需要感知保活定时器
- 客户主机崩溃,并且关闭或者正在重启。服务器总共发送10个探查,每个间隔75秒,如果没有任何响应,认为客户主机关闭并终止连接;
- 客户主机崩溃但已经重启。服务器收到保活探查的响应,但响应回会是个复位,使得服务器终止连接;
- 客户主机正常,但是服务不可达。与2类似,只能得到没有探查响应
缺点在于:1短暂差错可能使得一个好的连接被释放;2保活浪费不必要的带宽;
(4)时间等待计时器:Time_Wait Timer,也叫 2MSL定时器
在连接终止期使用,当TCP关闭连接时,并不认为这个连接就真正关闭了,在时间等待期间,连接还处于一种中间过度状态。这样就可以时重复的fin报文段在到达终点后被丢弃,这个计时器的值通常设置为一格报文段寿命期望值的两倍。
2MSL定时器测量一个连接处于TIME—WAIT黄台的时间,通常为2MSL(报文段寿命的两倍)。2MSL定时器的设置主要是为了确保发送的最后一个ACK报文段能够到达对方,并防止之前与本连接有关的由于延迟等原因而导致已失效的报文被误判为有效。
参考文献:《TCP/IP协议族》 Forouzan著,谢希仁译;
如何处理TCP连接中打开窗口的ACK丢失的情况?
关闭窗口的场景:接收方通告发送方接收的数据窗口为0,这个时候发送方不再发送数据;
打开窗口ACK丢失的危害:当接收方通告了一个窗口为非0的ACK,此ACK由于某种原因丢失,此时发送方在永远的等窗口打开的通知,接收方则永远的在等新数据的到来,这样有可能因为等待而造成连接关闭。
解决策略:使用坚持定时器,周期性的向接收方查询,以便发现窗口的变化
什么是糊涂窗口综合症?
对于服务器来讲,如果处理的速度过于缓慢,他会将通告窗口的值设置的越来越小,甚至是小于报文头,这种情况下,通信的效率极其低下,这种情况称作糊涂窗口综合症。
如何避免糊涂窗口综合症?
- 接收方:当窗口增加一个报文段大小(MSS)或者可以增加接收方缓存空间一半时,才通告窗口大小
- 发送方:当可以发送一个满长度的报文、发送至少是接收方通告窗口大小的一半的报文或者是可以发送任何数据并且不希望接收ACK(这种情况,数据都已经确认了)
Nagle算法中,当报文太小的时候就不发送,这里的小,可以看出发送方发送的报文小于报文段大小
TCP的4个定时器
对每个连接,TCP管理4个不同的定时器。
- 重传定时器使用于当希望收到另一端的确认。
TCP在发送一份数据后,启动重传定时器,在经过若干时间后如果没有收到ACK,则重传该数据,这个时间间隔通常如下:1s, 3s, 6s, 12s, 24, 48s, 和多个64s。这个指数关系被称为指数退避。TCP会一直尝试重传该数据,在9分钟以后放弃。
- 坚持定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
假定有如下情况:接收方接收到一定量的数据,发现本地缓存满了,于是发送一个窗口大小为0的ACK给发送方,发送方在接收到该ACK后停止发送。一段时间后,接收方有空余的窗口,于是发送一个窗口大小非0的ACK以更新窗口,不幸的是该ACK丢失了,于是整个连接被停止。坚持定时器正是为了解决这个问题,它在接收到一个窗口大小为0的ACK以后,启动一个定时器,定时去查询是否窗口已经更新(同样采用指针退避)。
- 保活定时器可检测到一个空闲连接的另一端何时崩溃或重启。
有如下情况,连接建立好以后,发送方不发送任何数据,这样一直下去(有可能是发送方系统崩溃,或者网络被意外切断),这就导致服务端一直占用该连接,浪费资源。保活定时器通常每两小时发送一个探测请求以查看对方是否存活。
- 2MSL定时器测量一个连接处于TIME_WAIT状态的时间。
2MSL时间是为了让TCP有时间发送最后一个ACK,防止该ACK丢失。
坚持定时器
一个例子
为了观察到实际中的坚持定时器,我们启动一个接收进程。它监听来自客户的连接请求,接受该连接请求,然后在从网上读取数据前休眠很长一段时间。
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秒。
![d5e7f5e6cb64e83ece403c1a90743b48.png](https://i-blog.csdnimg.cn/blog_migrate/5e4e74acfe42a2c6d1f85c416bf53530.jpeg)
为什么这些间隔总是比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秒发送一次,这个过程将持续到或者窗口被打开,或者应用进程使用的连接被终止。
总结
产生原因:
首先,接收端发送一个窗口大小为0的ACK报文;发送端收到该信号后,等待接收方发送窗口大小大于0的ACK.
然后,接收端发送窗口大小大于0的报文,但是该报文丢失,因为TCP并不对ACK报文进行确认,这就造成了
接收方等待接收数据,而发送方仍然等待窗口大于0的ACK,产生死锁。
![e0d6d642b76739b72cb4b883976b5cd9.png](https://i-blog.csdnimg.cn/blog_migrate/f1a271f2768b4cb6a2c95648ed684219.jpeg)