应用层对于每一个socket采用以下函数来开启 keepalive机制,其参数将采用系统上述配置。linux
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));编程
注意:keepalive是一个TCP协议包,并非应用层数据包,意即经过recv等函数从应用层上是没法得到该协议包。可经过抓包工具来看。windows
==================================================================服务器
1、什么是keepalive定时器?[1]网络
在一个空闲的(idle)TCP链接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,若是TCP链接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是咱们只要启动一个客户端进程,同服务器创建了TCP链接,无论你离开几小时,几天,几星期或是几个月,链接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要链接两端的主机没有重启,链接依旧保持创建。并发
这就能够认为不论是客户端的仍是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测链接的不活动状态(inactivity),从而引发任何一个应用程序的终止。然而有的时候,服务器须要知道客户端主机是否已崩溃而且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。app
存活定时器是一个包含争议的特征。许多人认为,即便须要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。此外,若是两个终端系统之间的某个中间网络上有链接的暂时中断,那么存活选项(option)就可以引发两个进程间一个良好链接的终止。例如,若是正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并不是如此。socket
存活(keepalive)并非TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引发一个良好链接(good connection)被释放(dropped),(2)它们消费了没必要要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。tcp
一些服务器应用程序可能表明客户端占用资源,它们须要知道客户端主机是否崩溃。存活定时器能够为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。函数
我的计算机用户使用TCP/IP协议经过Telnet登陆一台主机,这是可以说明须要使用存活定时器的一个经常使用例子。若是某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的链接。在图18.16,咱们看到如何在一个半打开链接上经过发送数据,获得一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。若是客户端消失,留给了服务器端半打开的链接,而且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开链接。
2、keepalive如何工做?[1]
在此描述中,咱们称使用存活选项的那一段为服务器,另外一端为客户端。也能够在客户端设置该选项,且没有不容许这样作的理由,但一般设置在服务器。若是链接两端都须要探测对方是否消失,那么就能够在两端同时设置(好比NFS)。
若在一个给定链接上,两小时以内无任何活动,服务器便向客户端发送一个探测段。(咱们将在下面的例子中看到探测段的样子。)客户端主机必须是下列四种状态之一:
1) 客户端主机依旧活跃(up)运行,而且从服务器可到达。从客户端TCP的正常响应,服务器知道对方仍然活跃。服务器的TCP为接下来的两小时复位存活定时器,若是在这两个小时到期以前,链接上发生应用程序的通讯,则定时器从新为往下的两小时复位,而且接着交换数据。
2) 客户端已经崩溃,或者已经关闭(down),或者正在重启过程当中。在这两种状况下,它的TCP都不会响应。服务器没有收到对其发出探测的响应,而且在75秒以后超时。服务器将总共发送10个这样的探测,每一个探测75秒。若是没有收到一个响应,它就认为客户端主机已经关闭并终止链接。
3) 客户端曾经崩溃,但已经重启。这种状况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引发服务器对链接的终止。
4) 客户端主机活跃运行,但从服务器不可到达。这与状态2相似,由于TCP没法区别它们两个。它所能代表的仅是未收到对其探测的回复。
服务器没必要担忧客户端主机被关闭而后重启的状况(这里指的是操做员执行的正常关闭,而不是主机的崩溃)。当系统被操做员关闭时,全部的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在链接上发送一个FIN。收到这个FIN后,服务器TCP向服务器进程报告一个文件结束,以容许服务器检测这种状态。
在第一种状态下,服务器应用程序不知道存活探测是否发生。凡事都是由TCP层处理的,存活探测对应用程序透明,直到后面2,3,4三种状态发生。在这三种状态下,经过服务器的TCP,返回给服务器应用程序错误信息。(一般服务器向网络发出一个读请求,等待客户端的数据。若是存活特征返回一个错误信息,则将该信息做为读操做的返回值返回给服务器。)在状态2,错误信息相似于“链接超时”。状态3则为“链接被对方复位”。第四种状态看起来像链接超时,或者根据是否收到与该链接相关的ICMP错误信息,而可能返回其它的错误信息。
windows 实现:
在一个正常的TCP链接上,当咱们用无限等待的方式调用下面的Recv或Send的时候:
ret=recv(s,&buf[idx],nLeft,flags);
或
ret=send(s,&buf[idx],nLeft,flags);
若是TCP链接被对方正常关闭,也就是说,对方是正确地调用了closesocket(s)或者shutdown(s)的话,那么上面的Recv或Send调用就能立刻返回,而且报错。这是因为closesocket(s)或者shutdown(s)有个正常的关闭过程,会告诉对方“TCP链接已经关闭,你不须要再发送或者接受消息了”。可是,若是是网线忽然被拔掉,TCP链接的任何一端的机器忽然断电或重启动,那么这时候正在执行Recv或Send操做的一方就会由于没有任何链接中断的通知而一直等待下去,也就是会被长时间卡住。这种情形解决的办法是启动TCP编程里的keepAlive机制。
struct TCP_KEEPALIVE inKeepAlive = {0}; unsigned long ulInLen = sizeof(struct TCP_KEEPALIVE); struct TCP_KEEPALIVE utKeepAlive = {0}; unsigned long ulOutLen = sizeof(struct TCP_KEEPALIVE); unsigned long ulBytesReturn = 0; inKeepAlive.onoff=1; inKeepAlive.keepaliveinterval=5000; //单位为毫秒 inKeepAlive.keepalivetime=1000; //单位为毫秒 ret=WSAIoctl(s, SIO_KEEPALIVE_VALS, (LPVOID)&inKeepAlive, ulInLen, (LPVOID)&outKeepAlive, ulOutLen, &ulBytesReturn, NULL, NULL);
此处的keepalivetime表示的是TCP链接处于畅通时候的探测频率,一旦探测包没有返回,就以keepaliveinterval的频率发送,通过若干次的重试,若是探测包都没有返回,那么就得出结论:TCP链接已经断开,因而上面的Recv或Send调用也就能立刻返回,不会无限制地卡住了。
上图是对上面文字的说明。亮条以前,TCP处于畅通状态,KeepAlive是以1000毫秒(keepalivetime的值)的频率发送探测包,在发送到第32个探测包的时候,探测包没有返回,因而就以5000毫秒(keepalivetime的值)的频率发送探测包,重发几回后,探测包都没有返回,因而就得出结论:此TCP链接已经断开了!
对于Win2K/XP/2003,能够从下面的注册表项找到影响整个系统全部链接的keepalive参数:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
“KeepAliveTime”=dword:006ddd00 “KeepAliveInterval”=dword:000003e8 “MaxDataRetries”=”5″
对于实用程序来讲,2小时的空闲时间太长。所以,咱们须要手工开启Keepalive功能并设置合理的Keepalive参数。在XP和WIN2003系统上,能够针对单独的socket来设置,可是在windows 2000,不能单独设置,若是设置,那么影响是整个系统的全部socket。
linux实现:
SO_KEEPALIVE/TCP_KEEPCNT/TCP_KEEPIDLE/TCP_KEEPINTVL 若是一方已经关闭或异常终止链接,而另外一方殊不知道,咱们将这样的TCP链接称为半打开的。TCP经过保活定时器(KeepAlive)来检测半打开链接。 在高并发的网络服务器中,常常会出现漏掉socket的状况,对应的结果有一种状况就是出现大量的CLOSE_WAIT状态的链接。这个时候,能够经过设置KEEPALIVE选项来解决这个问题,固然还有其余的方法能够解决这个问题,详细的状况能够查看参考资料8。
使用方法以下: //Setting For KeepAlive int keepalive = 1; setsockopt(incomingsock,SOL_SOCKET,SO_KEEPALIVE,(void*)(&keepalive),(socklen_t)sizeof(keepalive)); int keepalive_time = 30; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPIDLE,(void*)(&keepalive_time),(socklen_t)sizeof(keepalive_time)); int keepalive_intvl = 3; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPINTVL,(void*)(&keepalive_intvl),(socklen_t)sizeof(keepalive_intvl)); int keepalive_probes= 3; setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPCNT,(void*)(&keepalive_probes),(socklen_t)sizeof(keepalive_probes)); 设置SO_KEEPALIVE选项来开启KEEPALIVE,而后经过TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT设置keepalive的开始时间、间隔、次数等参数。 固然,也能够经过设置/proc/sys/net/ipv4/tcp_keepalive_time、tcp_keepalive_intvl和tcp_keepalive_probes等内核参数来达到目的,可是这样的话,会影响全部的socket,所以建议使用setsockopt设置。