TCP连接的双方即使在在没有数据交互的时候也不会终止双方的连接。只要它们两者之间的路由器不要宕机了,很久之后还是可以进行数据交互的。在有些时候,客户端或者服务器想要只当对方是否关闭了,但是因为有些原因FIN报文没有正确的发送过来。我们又想在双方之间保持少量是数据交互,让它们双方能够直到对方是否还“建在”。这种机制叫做keepalive(保活)。keepalive由一个定时器来控制,定时器超时以后发送keepalive probe,然后对方如果还在,那么就返回一个keepalive ACK。
It is driven by a keepalive timer. When the timer fires, a keepalive probe (keepalive for short) is sent, and the peer receiving the probe responds with an ACK.
Note:
Keepalive并不是属于TCP规范之一。RFC 1122表示:
1)会使得一个好的连接因为短暂的错误暂停,
2)浪费不必要的带宽
3)会更花钱hhhh
TCP keepalive是一个比较有争议的功能,因为很多人认为对另一方的轮询不应在TCP中实现,而是应该在应用层中实现。不过从另外一方面来说,基于TCP的应用层协议也可以共享keep alive的功能,而不需要字节额外去实现。TCP keep alive是可选的,你可以手动选择关闭或者打开。不过会导致,加入链路中的路由器出了点问题,那么TCP keepalive probe报文无法收到 ACK,那么就会使得它误认为对方已经宕机了,就会导致留下一个半开(half-open)连接。
The keepalive is an optionally enabled feature that can cause an otherwise good connection between two processes to be terminated because of a temporary loss of connectivity in the network joining the two end systems. For example, if the keepalive probes are sent during the time that an intermediate router has crashed and is rebooting, TCP incorrectly thinks its peer host has crashed.
比如说我们使用ssh程序登陆到了一台服务器,然后直接关机,没有退出ssh程序。虽然在前面我们说过使用一个半开连接来发送数据会返回一条RST报文,然而此时是在服务器保持了一个半开连接,在ssh中它永远不会主动的发送数据。所以keepalive可以使得服务器来检查到对方已经关闭了。
Description
连接的任何一方都可以使用keepalive,虽然keepalive在默认情况下是关闭的。
Either end of a TCP connection may request keepalives, which are turned off by default, for their respective direction of the connection.
有几个参数用于来控制keepalive:
keepalive time
:一般是多少秒,如果在keepalive time秒内,双方之间没有交换报文,那么就开始开始发送keepalive probe
keepalive interval
:如果在keepalive秒后发送了keep alive probe,但是没有响应。那么接下来每隔keep alive interval秒发送一个keep alive probe。
keepalive probes
:如果发送的keep alive probe次数达到了keep alive probes,那么就认为对方已经宕机了,于是连接也就终止了。
keepalive probe所发送的报文中的系列号是最近(most recent)收到的ACK号-1,因为这个字节已经被接收了,所以该数据的发送不会影响对方的接收缓存。对于接收方来说收到的probe是无害的,只要返回ACK来让对方知道连接还在。不过,小于接收窗口的报文不是会被直接丢弃吗?可能在KeepAlive有些不同吧。
the arriving segment does no harm, but it elicits an ACK that is used to determine whether the connection is still operating.
无论是keepalive probe还是返回的ACK都不包含着任何新的数据,所以TCP并不会保证它的可靠传输,即使丢失也不会重传,这也是为什么要设置keep alive probes
这个变量的原因。
TCP在使用keepalive的时候可能会遇到如下几种情况:
- 对方主机仍然可以响应。那么就是最常见的keepalive的情况,发送keepalive之后对方返回一个ACK,使得本地的keepalive timer重置,等于keep time。如果双方之间有任意的数据交互,那么也会将keepalive timer设置回keepalive time。
- 对方主机不可达,可能因为对方主机宕机了。所以keepalive probe回没有响应。接下来每隔keepalive interval发送一个probe,在最后所发送的keep alive probe个数达到了keepalive probes之后,一直都没收到response。那么就认为对方已经宕机了,直接关闭连接。
- 对方重启宕机了然后重启了,那么在发送keepalive probe的时候,对方会返回RST报文。那么这边就可以关闭连接了
- 如果对方不可达,是因为中间的链路出了问题。这个情况会和第二个情况一样处理,因为无法判断到底是中间链路出了问题还是说对方主机出了问题。
上面提到的几个变量是可以设置的,在Linux中,通过设置 如下变量:
net.ipv4.tcp_keepalive_time
:默认7200s=2h,多少秒之后发送keep alive probe
net.ipv4.tcp_keepalive_intvl
:默认75s,每次发送间隔多少秒
net.ipv4.tcp_keepalive_probes
:默认9次,总共发送几次后就认为连接已经坏了。
在windows也有与之类似的值,暂且不表。
在RFC 1122中,它要求对于keep alive的设置不能小于2h。此外keep alive不能启用,除非应用程序要求开启keep alive。
In addition, keepalives must not be enabled unless an application requests one
Keepalive Examples
接下来我们对上面所提到的2,3,4情形做实验。第一种情况会在这几种情况的操作中得到体现。
Other End Crashes
下面的例子是展示Server宕机,并没有重启。
- 使用的是windows client,在注册表中修改keepAliveTime为7s。
- 在windows使用ssh登录到Linux服务器,两者都启用keepalive。
- 双方之间传输数据
- 查看TCP每隔7秒发送keep alive,然后服务端返回ACK
- 服务器拔掉网线,这使得客户端认为对方已经掉线
- 然后接下来windows client每隔一秒发送window probe。
wireshark截图如下:
第1和2个分组数据交互结束后,就没有下一步的行动了。直到7秒后使得客户端发送keepalive probe报文。当它收到一条keepalive probe ACK之后,重置了keepalive timer,所以下一个keepalive probe发生在7秒后。当发送keepalive probe发送之后,一直没有ACK返回,那么接下来会每隔1s(1s是windows中默认的keepalive interval)发送keepalive probe。如下:
在接下来的10个window probe都没有返回ACK,那么TCP连接就断开了。此时客户端会认为对方不可达,而发送一条RST报文。
在本次例子当中,还有一些有意思的地方。如下图:
packet 9是client按下的键位,10是对这个键位的ACK。packet 11是echo 回来的字符,packet 12是对11的重传,packet 13是对echo的ACK。这里让人感觉奇怪的是为什么12会发生重传。除此之外,可以观察到在packet 11返回的ACK是50,而keep alive probe发送的seq 是49,验证了前面说过的probe 的seq为前面报文返回的ACK-1这一点。
在TCP Timeout and retransmission那一章,Linux method中曾经提到过Linux的RTO至少为为200 ms。上面红圈中两个报文的间隔时间超过200ms。作者提到,windows client和Linux server是在一个局域网内,所以packet 11丢失的可能性不大。所以,它的发生只可能是因为delayed AKC而导致的伪重传(spurious retransmission)。它和之前说到过的Nagle算法和delayed ACK组合在一起所导致的问题很相似。
Other End Crashes and Reboots
接下来是演示的,对方断开然后再重启的情况。这个比较简单,如图:
在packet 14之前都在交互数据。然后就暂停发送数据,在120s(重新设置过了keepalive time) 之后发送了keep alive probe。对方立即返回。然后将服务器网线拔掉,接着重启。到243s之后,客户端再发送了一条keep alive probe。但是此时对方重启好了,但是链接已经断开了,所以服务器返回了一条RST报文,来通知它把TCP连接关了。这和TCP connection management 中的half open有点相似。
PS:这里讲一下half-open和half-close的区别。half-close指的是调用了shutdown()函数,来使得一个TCP连接进入到half-open状态。然而half-open也可能是由abort造成的.
A TCP connection is said to be half-open if one end has closed or aborted the connection without the knowledge of the other end
虽然感觉差不多的,但是我认为half-close()更像是形容一种API,和close()相类是。而half-open表示的TCP连接的一种状态。多种原因会导致half-open,比如说系统宕机,shutdown(),拔掉网线。