TCP中的定时器

我们都知道TCP是可靠传输的,这其中很大一部分是因为TCP设立了很多定时器来帮助通信的双方来实现可靠传输。

  • TCP有一个超时重传的机制,于是这里就有一个重传定时器
  • 主动断开连接的一方会有一个TIME_WAIT状态,于是这里就有一个TIME_WAIT定时器
  • TCP为了处理长时间没有发生数据交互的连接,于是这里就有一个保活定时器
  • 另外在流量控制的时候,这里还有一个坚持定时器(具体的下面再说)

重传定时器

重传定时器是用来计算TCP每个报文的重传时间的。当主机A向主机B发送了一个报文之后,可能因为网络拥堵等原因,想要发送的报文并没有到达,这个时候当主机A在一段时间间隔之内没有收到主机B发来的确认应答,就认为超时了,这个时候就需要重传,而这里过多久才重传,这个一段时间间隔到底是多久,其实就是我们的重传定时器。

在达到重传定时器设定的时间以后,主机A就会重新发送一个相同的报文段,并将重传定时器归0。如果在规定的时间内收到了主机B发来的确认应答,就撤销对该报文的重传定时器。

于是有人会问了,那要是主机A一直收不到主机B的确认应答,可能因为网络拥堵的原因一直丢失,那主机A岂不是一直会不断地发送相同的报文给主机B,这样连接不就卡死在这里了吗?

很明显我们不会让上面的情况发生。在第一次重传还是没有收到主机B的确认应答以后,主机A就会将这个重传定时器的时间设置的长一点(一般都是2倍增长)。在Linux中,超时重传以500ms为单位进行控制,比如说第一次设置500ms,第二次设置1000ms,第三次就设置2000ms。当然不可能无限地一直往上增长,在累计一定的重传次数后,主机A就会认为网络或者对端主机出现异常,强制关闭连接。


TIME_WAIT定时器

TIME_WAIT定时器是主动断开连接的一方才会设置的一个定时器。

TIME_WAIT定时器设置的时间一般都是2msl(并且总是会等2msl),msl的意思是报文在一个传输方向上的最大生存时间,一般来说一个msl的值可能是30s,1min,2min。至于为什么要设置2msl,有以下两个原因

  • 保证在主动断开连接的一方在这个等待的时间内能够收到另一发发来的下一个FIN,因为它发送的最后一个ACK可能对方没有收到,可能会因为超时而重新继续发送FIN。在收到新的FIN之后,重置TIME_WAIT定时器
  • 保证在这个时间段内,所有的报文数据要么被接收,要么被丢弃(超过这个时间,传输的两个方向的数据生存的时间都超过了各自的msl,就都会被丢弃),避免下一个相同五元组(源IP,源端口,目的IP,目的端口,TCP)的连接会收到来自上一个连接的过期的数据

保活定时器

这个保活定时器,其实也就是TCP内部的keepalive机制,主要是为了应对TCP双方长时间都没有传输数据的情况。

如果客户端和服务器已经建立了连接,但此时客户端突然发生了网络故障,可能在很长一段时间这个网络故障都没有好,于是服务器在这段时间内是认为客户端暂时没有数据要发送给我,我也没有数据要发给你。

这段很长的时间,其实就是保活定时器的时间。在超过这段时间以后(一般保活定时器的时间都是2小时),服务器会想,怎么还没有给我发数据,客户端是不是出问题了啊,我不能傻等着啊。于是服务器就会发送一个keepalive探测包(包内没有数据)。

  • 如果客户端确实是没有数据要发送,连接一切正常,就会回给服务器一个ACK包,这样就会继续保持连接。
  • 如果连接出错了,比如客户端进程此时已经关闭,那么客户端就会回一个RST包,告诉服务器关闭连接。
  • 如果对方没有回复(进程没有关闭,但是网络出故障了),那么服务器就会每隔一定时间(这个时间可以设置)再发送keepalive探测包,如果连续多个包都被无视了,那么服务器就会认为客户端故障了,就会关闭这个连接。

坚持定时器

TCP通过让接收方将自己可以接收的缓冲区大小放到TCP首部的窗口大小字段来告诉发送方自己的接收缓冲区的大小,以此来达到流量控制。那么如果发送的这个窗口大小是0会怎么办呢?这将阻止发送方发送数据,直到接收窗口为非0为止。

扫描二维码关注公众号,回复: 3095945 查看本文章

假设碰到下面的情况,如果接收端在接收缓冲区为非0的情况,向发送方发送了一个窗口大小为非0的报文段。但是这个报文在传输中丢失了,发送端没有收到该报文段,就一直等待接收方发送非0窗口的报文通知,但是接收端并不知道报文已经丢了,而是觉得已经发送给发送端了,于是继续等待发送放发送数据,双方形成了一个你等我,我等你的循环等待的状态,形成了一个“死锁”。如果不采取任何措施,这个死锁将会一直持续下去。

这个是坚持定时器就出来发挥了。TCP为每一个连接都设置了一个坚持定时器。一旦TCP连接的一方收到了对方的零窗口报文,就会启动坚持定时器,若坚持定时器设置的时间到期,就发送一个零窗口探测报文段(该报文段只有一个字节的数据,它有一个序号,但该序号永远不需要确认,因此该序号可以持续重传),于是可能出现下面的情况

  • 对方在收到探测报文以后,在对该报文的确认中给出现在的窗口值,如果窗口值仍是0,那么发送探测报文的一方就将坚持定时器的时间重置,并将其持续的时间加倍。坚持定时器最大只能增加到约60秒,之后每次收到零窗口通知,坚持计数器的值就定位60秒。(注意这个时候只是重置了坚持定时器,并没有发送探测报文
  • 对方在收到探测报文以后,在对该报文的确认中给出现在的窗口值,如果窗口为非0,那么这个死锁的局面就被打破了
  • 在该探测报文发出以后,会同时启动重传定时器,如果重传定时器的时间到了,还没有收到接收方发来的响应,就超时重传探测报文

而对于坚持定时器与重传定时器不同的一点是,重传定时器在达到重传上限时,就会认为连接失效关闭连接。而在TCP一方接收到零窗口报文后,会一直不断地发送窗口探测,从不放弃,这些探查最多每隔60S发送一次,直到窗口被打开或是进程使用的连接终止

所以对上面的叙述总结一下,也就是说,如果在窗口探测的过程中,对方一直有响应,虽然响应可能每次都是零窗口报文,但是TCP不会放弃这个连接,会不断进行探测。但是如果在窗口探测的过程中,如果一直没有响应,这就触发了重传定时器,在达到重传上限时,会人认为连接失效关闭连接。

糊涂窗口综合征

对于窗口控制可能发生“糊涂窗口综合征”的问题。

TCP接收方的缓冲区已满,而交互式的应用程序进程一次只从接收缓冲区中读取1个字节的数据,然后再向发送方发起去人,并把窗口大小设为1个字节(但每次的TCP头部都要有20个固定字节)。发送方在收到这一个字节的窗口大小了,认为是非零窗口报文,于是取消了坚持定时器,然后发送一个TCP数据部分为1字节的报文。接收方发回确认,仍然将窗口设置为1个字节,这样进行下去,使TCP的效率很低

如何避免糊涂窗口综合征

接收方:不通告很小的窗口,而是等待一段时间,除非接收缓冲区已经可以容纳一个最长的报文段(MSS),或者等到接收缓冲区中已经有一般空闲的空间,只要出现这两种情况之一,就发送确认报文,并通知当前窗口大小;否则,通告为0

发送方:不要发送太小的报文段,而是把数据积累成足够大的报文段(MSS),或者达到接收方窗口的一半大小才发送


FIN_WAIT2定时器

在四次挥手的过程中,客户端发送FIN给服务器要求关闭连接,服务器响应ACK给客户端,客户端收到这个ACK进入FIN_WAIT2状态,并开始等待服务器发来的FIN报文。如果此时服务器一直不发这个FIN会怎么办呢?(比如一个小时),难道客户端就一直在这里傻等着吗?

看了上面的这么多定时器,当然我们就会想,这里难道没有一个定时器吗?还真有,Linux自然不会让这种情况发生。于是就有了一个FIN_WAIT2定时器。如果定时器超时仍然没有收到FIN,则关闭socket(客户端心里想,老子不等了!!!你还有的数据我也不要了=_=),发送一个RST报文。

注意,这里不会重传了,因为客户端已经发送给服务器FIN,并且服务器也已经回了一个ACK了,说明之前是有响应的。因此这里不会重传一个FIN给服务器了,而是在超时以后直接退出了。

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/82049133
今日推荐