keep-alive 和 TCP存活检测

什么是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 -------------->--->--->---|
      |---<---<---<------------ SYN/ACK ------------<---<---<---|
      |--->--->--->-------------- ACK -------------->--->--->---|
      |                                                         |
      |                                       system crash ---> X
      |
      |                                     system restart ---> ^
      |                                                         |
      |--->--->--->-------------- PSH -------------->--->--->---|
      |---<---<---<-------------- RST --------------<---<---<---|
      |                                                         |

  上面的场景就提出了一个需求,作为服务器端,可不能老是傻等是不是,当然timeout也是一个方案了,不过如果是IM这种类型的服务器,总不能用户一段时间没反应,就把它给踢了吧。

 

  keep-alive还有另外一个有点trick的作用,考虑另外一个场景,一般的终端总是位于防火墙或是NAT后面,而这些设备限制于硬件,不可能处理任意数量的连接,在内部队列满的时候,总是要把最不活跃的连接给砍了的。这个时候keep-alive就可以发挥最用了。看下面的图:

    _____           _____                                     _____
   |     |         |     |                                   |     |
   |  A  |         | NAT |                                   |  B  |
   |_____|         |_____|                                   |_____|
      ^               ^                                         ^
      |--->--->--->---|----------- SYN ------------->--->--->---|
      |---<---<---<---|--------- SYN/ACK -----------<---<---<---|
      |--->--->--->---|----------- ACK ------------->--->--->---|
      |               |                                         |
      |               | <--- connection deleted from table      |
      |               |                                         |
      |--->- PSH ->---| <--- invalid connection                 |
      |               |                                         |

程序上如何使用呢?
  无论是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,从而得知连接已失效,客户端程序便有机会及时执行清除工作、提醒用户或重新连接。

posted @ 2010-12-24 17:35  hjtc  Views(2248)  Comments(0Edit  收藏  举报