TCP-Timer

TCP 定时器

最为人熟知的TCP定时器应该就是超时重传定时器了。超时重传定时器用在当发送方发送的TCP包之后为会该TCP包开启一个定时器,到时候如果还没有收到该TCP包的ACK包则重发一次该TCP包。
超时重传定时器好理解,在TCP通信的场景也比较容易出现。除此之外,有两种重要的TCP定时器虽然出现的概率较小,但是对TCP的功能来说非常重要。

坚持定时器(Persist Timer)

我们知道,TCP连接的发送方需要接收方发回的ACK包来更新自己的拥塞控制窗口。考虑这样一种情况,因为一个ACK丢失了,便使得TCP连接的双方陷入了死锁的状态:接收方因为已经向发送方通告了一个非0的窗口而等待接收方发来的数据,但同时发送方又在等待接收方发来的ACK包来进行窗口更新。(需要注意的是,这只是坚持定时器处理的可能的场景之一而非唯一场景)。
为了解决这种问题,TCP的发送方使用一个坚持定时器来周期性地向接收方进行查询,以便发现窗口是否增大。这种从发送方发出的报文段称为窗口探查。下图是一个实际的例子:

在本例中,发送方是180.163.68.11,接收方是192.168.1.100。
可以看到,接收方在 21:37:08:299726 之后回给发送方的ACK包中的通告窗口就一直是0。而发送方在 21:37:08:299726 之后,一直在发送一个 “ack 114“ (这是最开始时,192.168.1.100 请求 180.163.68.11 建立TCP连接的TCP包的ISN) 并通告自己窗口的TCP包给接收方,但是接收方ACK给发送方的窗口通告中一直是0。这就是一个很明显的窗口探查的例子。
同时,发送方的每一个窗口探查周期(发送方问 - 接收方回)的时间间隔在不断增大。从第一个间隔0.43s,到第二个间隔0.87s,再到第三个间隔1.73….这个现象也被称为TCP指数退避,只不过这里的基数x是0.43s。
虽然在例子中没有显示,但是需要注意的是:TCP从不放弃窗口探查,窗口探查过程将持续到窗口打开或者连接被应用进程终止。

保活定时器

对于TCP连接来说,是可以没有任何数据流过而依旧保持连接建立的状态的。因此便会出现一种情况:在TCP连接一方的主机突然崩溃和没有向另一方发送FIN/RST包时,又或者TCP连接的一方的网线断开同时TCP连接上没有在传输信息时,TCP连接实际上已经被某一方断开了,而另一方却不知道还一直认为TCP连接处于连接建立的状态。
保活定时器就是为了探测这种半开放的连接而存在的。很多时候为服务器端所使用,以便在探测到客户端崩溃之后断开连接,释放掉提供给应用程序所使用的资源。
值得一提的是,定时保活功能很多时候使用的是应用程序级的定时器而不是TCP定时器。例如,各家的长连接网络通信框架基本都有心跳保活功能。与TCP定时器相比,应用级别的定时器可以获取更多客户机的信息。
总体来说,TCP定时保活器可以探知客户机是否处于以下三种状态之一:

  • 客户机正常运行,并且从服务器可达。这个时候服务器会收到客户机回传的ACK包。服务器等待下一个定时器周期到来重新探测。
  • 客户机崩溃关闭,也可能是客户机的物理线路或者数据链路出现了问题,导致从服务器不可达。虽然某些角度来说这是三种不同的问题,但是从服务器的角度观察是一样的 —— 客户机不可达
  • 客户机崩溃并且重新启动过。对于服务器发送的保活探查,客户机会响应一个带有RST标识的ACK给服务器,使得服务器可以终止这个TCP连接。

相信有过相关工作经验的工程师都清楚在连接保持中对这三种状态探查的重要性,即便到了应用程序级别的保活定时器,也是最基础的功能。