linux下使用TCP存活(keepalive)定时器

1、什么是keepalive定时器?[1]linux

在 一个空闲的(idle)TCP链接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,若是TCP链接两端没有任何一个进程在向对 方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就 是咱们只要启动一个客户端进程,同服务器创建了TCP链接,无论你离开几小时,几天,几星期或是几个月,链接依旧存在。中间的路由器可能崩溃或者重启,电 话线可能go down或者back up,只要链接两端的主机没有重启,链接依旧保持创建。express

这 就能够认为不论是客户端的仍是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测链接的不活动状态 (inactivity),从而引发任何一个应用程序的终止。然而有的时候,服务器须要知道客户端主机是否已崩溃而且关闭,或者崩溃但重启。许多实现提供 了存活定时器来完成这个任务。编程

存 活定时器是一个包含争议的特征。许多人认为,即便须要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。此外,若是两个终端系统 之间的某个中间网络上有链接的暂时中断,那么存活选项(option)就可以引发两个进程间一个良好链接的终止。例如,若是正好在某个中间路由器崩溃、重 启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并不是如此。服务器

存 活(keepalive)并非TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引发一个良好链接(good connection)被释放(dropped),(2)它们消费了没必要要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的 实现中提供了存活定时器。网络

一些服务器应用程序可能表明客户端占用资源,它们须要知道客户端主机是否崩溃。存活定时器能够为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。app

个 人计算机用户使用TCP/IP协议经过Telnet登陆一台主机,这是可以说明须要使用存活定时器的一个经常使用例子。若是某个用户在使用结束时只是关掉了电 源,而没有注销(log off),那么他就留下了一个半打开(half-open)的链接。在图18.16,咱们看到如何在一个半打开链接上经过发送数据,获得一个复位 (reset)返回,但那是在客户端,是由客户端发送的数据。若是客户端消失,留给了服务器端半打开的链接,而且服务器又在等待客户端的数据,那么等待将 永远持续下去。存活特征的目的就是在服务器端检测这种半打开链接。

2、keepalive如何工做?[1]
less

在此描述中,咱们称使用存活选项的那一段为服务器,另外一端为客户端。也能够在客户端设置该选项,且没有不容许这样作的理由,但一般设置在服务器。若是链接两端都须要探测对方是否消失,那么就能够在两端同时设置(好比NFS)。socket

若在一个给定链接上,两小时以内无任何活动,服务器便向客户端发送一个探测段。(咱们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:
tcp

1) 客户端主机依旧活跃(up)运行,而且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,若是在这两个小时到期以前,链接上发生应用程序的通讯,则定时器从新为往下的两小时复位,而且接着交换数据。ide

2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程当中。在这两种状况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,而且在75秒以后超时。服务器将总共发送10个这样的探测,每一个探测75秒。若是没有收到一个响应,它就认为客户端主机已经关闭并终止链接。

3) 客户端曾经崩溃,但已经重启。这种状况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引发服务器对链接的终止。

4) 客户端主机活跃运行,但从服务器不可到达。这与状态2相似,由于TCP没法区别它们两个。它所能代表的仅是未收到对其探测的回复。

服 务器没必要担忧客户端主机被关闭而后重启的状况(这里指的是操做员执行的正常关闭,而不是主机的崩溃)。当系统被操做员关闭时,全部的应用程序进程(也就是 客户端进程)都将被终止,客户端TCP会在链接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以容许服务器检测这种状 态。

在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP 层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,经过服务器的TCP,返回给服务器应用程序错误信息。(一般服务器 向网络发出一个读请求,等待客户端的数据。若是存活特征返回一个错误信息,则将该信息做为读操做的返回值返回给服务器。)在状态2,错误信息相似于“链接 超时”。状态3则为“链接被对方复位”。第四种状态看起来像链接超时,或者根据是否收到与该链接相关的ICMP错误信息,而可能返回其它的错误信息。

3、在Linux中如何使用keepalive?[2]

Linux has built-in support for keepalive. You need to enable TCP/IP networking in order to use it. You also need procfs support andsysctl support to be able to configure the kernel parameters at runtime.

The procedures involving keepalive use three user-driven variables:

  • tcp_keepalive_time

  • the interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe; after the connection is marked to need keepalive, this counter is not used any further

  • tcp_keepalive_intvl

  • the interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime

  • tcp_keepalive_probes

  • the number of unacknowledged probes to send before considering the connection dead and notifying the application layer

Remember that keepalive support, even if configured in the kernel, is not the default behavior in Linux. Programs must request keepalive control for their sockets using the setsockopt interface. There are relatively few programs implementing keepalive, but you can easily add keepalive support for most of them following the instructions.

上 面一段话已经说得很明白,linux内核包含对keepalive的支持。其中使用了三个参数:tcp_keepalive_time(开启 keepalive的闲置时长)tcp_keepalive_intvl(keepalive探测包的发送间隔) 和tcp_keepalive_probes (若是对方不予应答,探测包的发送次数);如何配置这三个参数呢?

There are two ways to configure keepalive parameters inside the kernel via userspace commands:

  • procfs interface

  • sysctl interface

We mainly discuss how this is accomplished on the procfs interface because it's the most used, recommended and the easiest to understand. The sysctl interface, particularly regarding the sysctl(2) syscall and not the sysctl(8) tool, is only here for the purpose of background knowledge.

The procfs interface

This interface requires both sysctl and procfs to be built into the kernel, and procfs mounted somewhere in the filesystem (usually on/proc, as in the examples below). You can read the values for the actual parameters by "catting" files in /proc/sys/net/ipv4/directory:

  # 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

The first two parameters are expressed in seconds, and the last is the pure number. This means that the keepalive routines wait for two hours (7200 secs) before sending the first keepalive probe, and then resend it every 75 seconds. If no ACK response is received for nine consecutive times, the connection is marked as broken.

Modifying this value is straightforward: you need to write new values into the files. Suppose you decide to configure the host so that keepalive starts after ten minutes of channel inactivity, and then send probes in intervals of one minute. Because of the high instability of our network trunk and the low value of the interval, suppose you also want to increase the number of probes to 20.

Here's how we would change the settings:

  # echo 600 > /proc/sys/net/ipv4/tcp_keepalive_time  # echo 60 > /proc/sys/net/ipv4/tcp_keepalive_intvl  # echo 20 > /proc/sys/net/ipv4/tcp_keepalive_probes

To be sure that all succeeds, recheck the files and confirm these new values are showing in place of the old ones.

这样,上面的三个参数配置完毕。使这些参数重启时保持不变的方法请阅读参考文献[2]。

4、在程序中如何使用keepalive?[2]-[4]

All you need to enable keepalive for a specific socket is to set the specific socket option on the socket itself. The prototype of the function is as follows:

int setsockopt(int s, int level, int optname,                 const void *optval, socklen_t optlen)

The first parameter is the socket, previously created with the socket(2); the second one must be SOL_SOCKET, and the third must beSO_KEEPALIVE . The fourth parameter must be a boolean integer value, indicating that we want to enable the option, while the last is the size of the value passed before.

According to the manpage, 0 is returned upon success, and -1 is returned on error (and errno is properly set).

There are also three other socket options you can set for keepalive when you write your application. They all use the SOL_TCP level instead of SOL_SOCKET, and they override system-wide variables only for the current socket. If you read without writing first, the current system-wide parameters will be returned.

  • TCP_KEEPCNT: overrides tcp_keepalive_probes

  • TCP_KEEPIDLE: overrides tcp_keepalive_time

  • TCP_KEEPINTVL: overrides tcp_keepalive_intvlint keepAlive = 1; // 开启keepalive属性

    咱们看到keepalive是一个开关选项,能够经过函数来使能。具体地说,可使用如下代码:

    setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));

    上面英文资料中提到的第二个参数能够取为SOL_TCP,以设置keepalive的三个参数(具体代码参考文献[3]),在程序中实现须要头文件“netinet/tcp.h”。固然,在实际编程时也能够采用系统调用的方式配置的keepalive参数。

    关于setsockopt的其余参数能够参考文献[4]。

    5、如何判断TCP链接是否断开?[3]

    当tcp检测到对端socket再也不可用时(不能发出探测包,或探测包没有收到ACK的响应包),select会返回socket可读,而且在recv时返回-1,同时置上errno为ETIMEDOUT。

网上提到 “ET(edge-triggered)是高速工做方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核经过epoll告诉你。而后它会假设你知道文件描述符已经就绪,而且不会再为那个文件描述符发送更多的就绪通知,直到你作了某些操做致使那个文件描述符再也不为就绪状态了”。

编写代码测试了一下:

若是客户端发送100字节,服务器每次只读取10字节,若是epoll_wait设置了超时而且客户端没有再发送数据,到达超时时间后还会有IN事件触发,若是设置了永不超时(epoll_wait(epfd,events,20,-1)),而且客户端再也不发送数据就不会产生IN事件。若是客户端继续发送数据会产生IN事件或者服务器再次mod IN事件就会有IN事件到达,这样能够读取没有读完的数据。


在IN事件后设置OUT,可是OUT中再也不设置IN,若是客户端发送数据,则会触发OUT事件。

if(events[i].events&EPOLLIN)
 {                
        ev.data.fd=sockfd;             
        ev.events=EPOLLOUT|EPOLLET;              
        epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
             
}  else if(events[i].events&EPOLLOUT)
 {
       cout << "EPOLLOUT" << endl;            
       //ev.data.fd=sockfd;
        //设置用于注测的读操做事件
        // ev.events=EPOLLIN|EPOLLET;
       //修改sockfd上要处理的事件为EPOLIN
       // epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}

若是客户端屡次发送了数据,server单线程在第一次触发IN事件后server在一直读取直到错误,就算客户端屡次发送只要服务器收取到就不会再次触发IN事件。

单线程代码以下:

if(events[i].events&EPOLLIN)
{
       printf("EPOLLIN\n);    
        sockfd = events[i].data.fd;
        char buf;
        sleep(3);
         while (read(sockfd, &buf, 1) > 0) 
        {
            printf("%c\n",buf);
        }

}


对于客户端程序关闭socket或异常关闭,会触发IN事件,recv返回0。

须要补充关机、拔掉网线、断电等状况,考虑设置keep-alive与不设置keep-alive分别测试。

阻塞socket状况下:

(1)无Keep-alive状况下,断电与拔网线不会收到IN事件;关机与程序退出同样会触发IN事件,read返回0;

(2)在客户端设置keep-alive,对服务器没有影响,不会产生in事件。

(3) 在服务器设置keep-alive,若是拔掉网线,在TCP_KEEPINTVL*TCP_KEEPCNT左右会产生in事件,若是没有有数据,recv 返回-1,错误信息为Connect time out(ETIMEDOUT);若是内核接收队列还有数据只会触发一次in事件,recv返回读出的字节数,若是继续读取会获得Connect time out。

非阻塞socket状况:

(1)不拔掉网线,接受in事件后循环读,若是再没有数据可读会返回EAGAIN Resource temporarily unavailable (may be the same value as EWOULDBLOCK)

(2)其余状况同阻塞socket


设置keep-alive代码以下:

int socket_set_keepalive( int fd){    int ret, error, flag, alive, idle, cnt, intv;        /* Set: use keepalive on fd */    alive = 1;    if (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &alive, sizeof alive) != 0)    {        printf ("Set keepalive error: %s.\n" , strerror (errno));        return -1;    }        /* 10秒钟无数据,触发保活机制,发送保活包 */    idle = 10;    if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE , &idle, sizeof idle) != 0)    {        printf ("Set keepalive idle error: %s.\n" , strerror (errno));        return -1;    }        /* 若是没有收到回应,则5秒钟后重发保活包 */    intv = 5;    if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL , &intv, sizeof intv) != 0)    {        printf ("Set keepalive intv error: %s.\n", strerror (errno));        return -1;    }        /* 连续3次没收到保活包,视为链接失效 */    cnt = 3;    if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT , &cnt, sizeof cnt) != 0)    {        printf ("Set keepalive cnt error: %s.\n", strerror (errno));        return -1;    }        return 0;}

相关文章
相关标签/搜索