什么是keep-alive?
顾名思义即可,注意它只适用于TCP连接。系统会替你维护一个timer,时间到了,就会向remote peer发送一个probe package,当然里面是没有数据的,对方就会返回一个应答,这时你就知道这个通道保持正常。
有什么用呢?
考虑下面这个场景,端点A和端B开始连接,三次握手,建立好了一个稳定的双向通道。然后双方发送完初始的数据后,进入等待状态。这时候,拔掉B的电源插头,B死掉了,它不可能有任何机会向A发送FIN包,或是其他数据来说明自己挂掉,需要终止连接。因此A只是简单的继续等待。然后重启机器和应用程序,B又恢复原始的状态,可是原本的连接已经没有了,之后就可能有三种情况,双方继续傻等,或是B向A发起新的请求,最可能的还是A向B发送新的数据,这时B因为原有的socket已经死了,就返回一个RST,A关闭连接。看下面的图: _____ _____
| | | |
| A | | B |
|_____| |_____|
^ ^
|--->--->--->-------------- SYN -------------->--->--->---|
|---
|--->--->--->-------------- ACK -------------->--->--->---|
| |
| system crash ---> X
|
| system restart ---> ^
| |
|--->--->--->-------------- PSH -------------->--->--->---|
|---
| |
上面的场景就提出了一个需求,作为服务器端,可不能老是傻等是不是,当然timeout也是一个方案了,不过如果是IM这种类型的服务器,总不能用户一段时间没反应,就把它给踢了吧。
keep-alive还有另外一个有点trick的作用,考虑另外一个场景,一般的终端总是位于防火墙或是NAT后面,而这些设备限制于硬件,不可能处理任意数量的连接,在内部队列满的时候,总是要把最不活跃的连接给砍了的。这个时候keep-alive就可以发挥最用了。看下面的图:
_____ _____ _____
| | | | | |
| A | | NAT | | B |
|_____| |_____| |_____|
^ ^ ^
|--->--->--->---|----------- SYN ------------->--->--->---|
|---
|--->--->--->---|----------- ACK ------------->--->--->---|
| | |
| |
| | |
|--->- PSH ->---|
| | |
程序上如何使用呢?
无论是windows还是linux都实现了这个功能,你只需要设置socket的属性即可,当然你也可自己实现这个功能,也就是定期发送心跳包.
先说linux平台:
一般的实现如下,也就是说每过7200s空闲(两个小时),就会发送一个探测包,如果没有应答,就隔75s,再次发送,连续9次没有反应,就判定该连接死了。
# cat /proc/sys/net/ipv4/tcp_keepalive_time
7200
# cat /proc/sys/net/ipv4/tcp_keepalive_intvl
75
# cat /proc/sys/net/ipv4/tcp_keepalive_probes
9
如何改呢,可以用直接该系统默认值,则全局都会有效,这似乎不大合适。 如果是程序中,则只要设置socket的属性就行了,调用setsockopt,首先打开keep-alive功能:
int optval = 1;
socklen_t optlen = sizeof(optval);
if(setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
perror("setsockopt()");
close(s);
exit(EXIT_FAILURE);
}
然后设定要改的值,像这样:
setsockopt (fd, SOL_TCP, TCP_KEEPIDLE, &newValue, optlen);
TCP_KEEPCNT: 代表 tcp_keepalive_probes
TCP_KEEPIDLE: 代表 tcp_keepalive_time
TCP_KEEPINTVL: 代表 tcp_keepalive_intvl
windows平台上也是差不多:
windows中也是可以做出系统级别的调整的, 对于Win2K/XP/2003,可以从下面的注册表项找到影响整个系统所有连接的参数:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
“KeepAliveTime”=dword:006ddd00
“KeepAliveInterval”=dword:000003e8
“MaxDataRetries”=”5″
在程序中来设定的话,首先先打开keep-alive,跟在linux中是一样的,
BOOL bKeepAlive = TRUE;
int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));
然后调整具体的参数,需要调用WSAIoctl
tcp_keepalive alive_in = {0};
tcp_keepalive alive_out = {0};
alive_in.keepalivetime = 5000;
alive_in.keepaliveinterval = 1000;
alive_in.onoff = TRUE;
unsigned long ulBytesReturn = 0;
nRet = WSAIoctl(socket_handle, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in), &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);
开启Keepalive选项之后,对于使用IOCP模型的服务器端程序来说,一旦检测到连接断开,GetQueuedCompletionStatus函数将立即返回FALSE,使得服务器端能及时清除该连接、释放该连接相关的资源。对于使用 select模型的客户端来说,连接断开被探测到时,以recv目的阻塞在socket上的select方法将立即返回SOCKET_ERROR,从而得知连接已失效,客户端程序便有机会及时执行清除工作、提醒用户或重新连接。