概述
方法1:应用层自己实现的心跳包
由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时 向客户端发送一个短小精悍的数据包,然后启动一个低级别的线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没 有收到服务器的心跳包,则认为连接不可用。
方法2:TCP的KeepAlive保活机制
因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。 不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。
(我们知道,TCP有一个连接检测机制,就是如果在指定的时间内(一般为2个小时)没有数据传送,会给对端发送一个Keep-Alive数据报,使用的序列号是曾经发出的最后一个报文的最后一个字节的序列号,对端如果收到这个数据,回送一个TCP的ACK,确认这个字节已经收到,这样就知道此连接没有被断开。如果一段时间没有收到对方的响应,会进行重试,重试几次后,向对端发一个reset,然后将连接断掉。在Windows中,第一次探测是在最后一次数据发送的两个小时,然后每隔1秒探测一次,一共探测5次,如果5次都没有收到回应的话,就会断开这个连接。)
对于Win2K/XP/2003,可以从下面的注册表项找到影响整个系统所有连接的keepalive参数:
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters]
“KeepAliveTime”=dword:006ddd00
“KeepAliveInterval”=dword:000003e8
“MaxDataRetries”=”5″
心跳包机制
1客户端每隔一个时间间隔发生一个探测包给服务器
2客户端发包时启动一个超时定时器
3服务器端接收到检测包,应该回应一个包
4如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
转自:http://blog.sina.com.cn/s/blog_a459dcf5010153m5.html
根据上面的介绍我们可以知道对端以一种非优雅的方式断开连接的时候,我们可以设置SO_KEEPALIVE属性使得我们在2小时以后发现对方的TCP连接是否依然存在。
具体操作:
//设置KeepAlive
1、 BOOL bKeepAlive = TRUE;
int nRet=::setsockopt(sockClient,SOL_SOCKET,SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive));
if(nRet!=0)
{
AfxMessageBox("出错");
return ;
}
2、感觉两小时时间太长可以自行设定方法1
//设置KeepAlive检测时间和次数
tcp_keepalive inKeepAlive = {0}; //输入参数
unsigned long ulInLen = sizeof(tcp_keepalive );
tcp_keepalive outKeepAlive = {0}; //输出参数
unsigned long ulOutLen = sizeof(tcp_keepalive );
unsigned long ulBytesReturn = 0;
//设置socket的keep alive为10秒,并且发送次数为3次
inKeepAlive.onoff = 1;
inKeepAlive.keepaliveinterval = 4000; //两次KeepAlive探测间的时间间隔
inKeepAlive.keepalivetime = 1000; //开始首次KeepAlive探测前的TCP空闭时间
nRet=WSAIoctl(sockClient,
SIO_KEEPALIVE_VALS,
(LPVOID)&inKeepAlive,
ulInLen,
(LPVOID)&outKeepAlive,
ulOutLen,
&ulBytesReturn,
NULL,
NULL);
if(SOCKET_ERROR == nRet)
{
AfxMessageBox("出错");
return;
}
3、感觉两小时时间太长可以自行设定方法2
因此我们可以得到
int keepIdle = 6;
int keepInterval = 5;
int keepCount = 3;
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
详见:http://blog.csdn.net/gavin1203/article/details/5290609
对setsockopt的操作,详见:http://www.cnblogs.com/hateislove214/archive/2010/11/05/1869886.html
-------------------------------------------------------------------------------------------------------------------------
应用层对于每个socket采用如下函数来开启 keepalive机制,其参数将采用系统上述配置。
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
注意:keepalive是一个TCP协议包,并不是应用层数据包,意即通过recv等函数从应用层上是无法获得该协议包。可通过抓包工具来看。
==================================================================
一、什么是keepalive定时器?[1]
在一个空闲的(idle)TCP连接上,没有任何的数据流,许多TCP/IP的初学者都对此感到惊奇。也就是说,如果TCP连接两端没有任何一个进程在向对方发送数据,那么在这两个TCP模块之间没有任何的数据交换。你可能在其它的网络协议中发现有轮询(polling),但在TCP中它不存在。言外之意就是我们只要启动一个客户端进程,同服务器建立了TCP连接,不管你离开几小时,几天,几星期或是几个月,连接依旧存在。中间的路由器可能崩溃或者重启,电话线可能go down或者back up,只要连接两端的主机没有重启,连接依旧保持建立。
这就可以认为不管是客户端的还是服务器端的应用程序都没有应用程序级(application-level)的定时器来探测连接的不活动状态(inactivity),从而引起任何一个应用程序的终止。然而有的时候,服务器需要知道客户端主机是否已崩溃并且关闭,或者崩溃但重启。许多实现提供了存活定时器来完成这个任务。
存活定时器是一个包含争议的特征。许多人认为,即使需要这个特征,这种对对方的轮询也应该由应用程序来完成,而不是由TCP中实现。此外,如果两个终端系统之间的某个中间网络上有连接的暂时中断,那么存活选项(option)就能够引起两个进程间一个良好连接的终止。例如,如果正好在某个中间路由器崩溃、重启的时候发送存活探测,TCP就将会认为客户端主机已经崩溃,但事实并非如此。
存活(keepalive)并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:(1)在短暂的故障期间,它们可能引起一个良好连接(good connection)被释放(dropped),(2)它们消费了不必要的宽带,(3)在以数据包计费的互联网上它们(额外)花费金钱。然而,在许多的实现中提供了存活定时器。
一些服务器应用程序可能代表客户端占用资源,它们需要知道客户端主机是否崩溃。存活定时器可以为这些应用程序提供探测服务。Telnet服务器和Rlogin服务器的许多版本都默认提供存活选项。
个人计算机用户使用TCP/IP协议通过Telnet登录一台主机,这是能够说明需要使用存活定时器的一个常用例子。如果某个用户在使用结束时只是关掉了电源,而没有注销(log off),那么他就留下了一个半打开(half-open)的连接。在图18.16,我们看到如何在一个半打开连接上通过发送数据,得到一个复位(reset)返回,但那是在客户端,是由客户端发送的数据。如果客户端消失,留给了服务器端半打开的连接,并且服务器又在等待客户端的数据,那么等待将永远持续下去。存活特征的目的就是在服务器端检测这种半打开连接。
二、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_MACHINESYSTEMCurrentControlSetServicesTcpipParameters]
“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设置。
------------------------------------------------------------------------------------------------------------------------------------------------------------
BOOL SetSockOpt( int nOptionName, const void* lpOptionVlaue, int nOptionLen, int nLevel = SOL_SOCKET );
返回值:
调用成功时,返回非零值,否则为0,并可以调用GetLastError取得特定的错误代码。此成员函数可用的错误代码有:
· | WSANOTINITIALISED | 在调用本API函数之前,必须已经成功地执行AfxSocketInit。 |
· | WSAENETDOWN | Windows Sockets检测到网络系统故障。 |
· | WSAEFAULT | lpOptionValue不是进程地址空间中的有效值。 |
· | WSAEINPROGRESS | 正在执行一个成块的Windows Sockets操作。 |
· | WSAEINVAL | nLevel无效,或者lpOptionValue中的信息无效。 |
· | WSAENETRESET | 当设置了SO_KEEPALIVE时,连接超时。 |
· | WSAENOPROTOOPT | 系统不支持该选项。SOCK_STREAM型的套接字不支持SO_BROADCAST,SOCK_DGRAM型的套接字不支持SO_DONTLINGER,SO_KEEPALIVE,SO_LINGER和SO_OOBINLINE等选项。 |
· | WSAENOTCONN | 当设置了SO_KEEPALIVE时,连接已经被重置 |
· | WSAENOTCONN | 套接字没有连接上(仅用于SOCK_STREAM型的套接字)。 |
· | WSAENOTSOCK | 描述符不是一个套接字。 |
参数:
nOptionName | 准备设置值的套接字选项。 |
lpOptionValue | 指向待设置的值所在缓冲的指针。 |
nOptionLen | lpOptionValue缓冲的字节数。 |
nLevel | 选项定义所在的级别,系统支持的级别只有SOL_SOCKET和IPPROTO_TCP。 |
说明:
本函数用于设置套接字的选项。它可以设置任何类型和状态的套接字的选项,改变它们的当前值。虽然选项可以存在于协议的多个级别,本函数只设置协议的最高级别(socket)的选项。选项会影响套接字的操作,例如是否允许在普通数据流中接收快速传输的数据,是否允许在套接字中发送广播消息等等。
套接字选项分为两种:布尔型的选项(允许或者禁止某一功能),整数型或者结构型的选项。要允许某个布尔型的选项,lpOptionVlaue只需指向一个非零整数。禁止该选项时,lpOptionValue就指向一个等于0的整数。对布尔型的选项来说,nOptionLen应该和sizeof(BOOL)相等。对其它的选项来说,lpOptionValue指向包含了选项所需值的整数或者结构,nOptionLen则指明整数类型或者结构的长度。
SO_LINGER用于控制未发送数据在套接字上的排队方式,以及调用Close函数关闭套接字时的行为。要了解更详细的信息,请参阅联机文档“Win32 SDK”中的“Windows套接字编程注意事项”。
缺省时,套接字不能被绑定到一个正在使用的本地地址上。然而,在某些场合,希望能重用这些地址。既然每个连接是由本地地址和远地地址共同来唯一确定的,保持远地地址不同,同时让两个套接字绑定到同一个本地地址是完全可行的。
如果调用Bind时的地址已经被另一个套接字使用, Windows Sockets的实现会禁止这个绑定。为了避免这个情况,从而实现地址的重用,就需要在调用Bind之前,设置套接字选项SO_REUSEADDR。该选项只在调用Bind时才起作用。没有必要把一个没有重用地址的套接字的选项设为SO_REUSEADDR,在调用Bind后设置或者重置这个选项对任何套接字都不会发生影响。
应用可以设置SO_KEEPALIVE选项,从而可以使用Windows Sockets实现所提供的传输控制协议(TCP)的“保持活动”包(关于“keep-alive”包的详细内容,请参阅联机文档“Win32 SDK”中的“Windows套接字编程注意事项”)。Windows Sockets的具体实现不一定要支持“保持活动”包。如果要支持的话,它的精确语义可由实现定义,但必须和RFC1122中4.2.3.6节“Internet主机——通信层需求”阐述的内容一致。如果因为“保持活动”而删除了一个连接,在这个套接字上的任何调用都返回错误代码WSAENETRESET,以后的调用则返回错误代码WSAENOTCONN。
设置TCP_NODELAY选项可以禁止Nagle算法。Nagle算法缓冲主机发送的那些未被确认的小数据包,并组成一个大的数据包,从而减少了发送的数据包个数。然而,对某些应用来说,这样会影响效率,可以设置TCP_NODELAY来禁止Nagle算法。应用编程轻易不要把这个选项设置为TCP_NODELAY,因为它会对网络性能产生较大的负面影响。TCP_NODELAY是支持的唯一一个IPPROTO_TCP级别的套接字选项。Windows Sockets的某些实现可以通过设置SO_DEBUG选项来支持调试信息的输出。
SetSockOpt支持的选项如下表,类型列指的是lpOptionValue指向的数据类型。
值 | 类型 | 含义 |
SO_BROADCAST | BOOL | 允许在套接字上传输广播消息 |
SO_DEBUG | BOOL | 记录调试信息 |
SO_DONTLINGER | BOOL | 不成块等待未发送数据发送完的Close调用;设置本选项和在l_onofff=0时设置SO_LINGER等价 |
SO_DONTROUTE | BOOL | 不经路由转换:直接把数据发到接口 |
SO_KEEPALIVE | BOOL | 发送keep_alives |
SO_LINGER | struct LINGER | 如果有未发送数据,则在Close时延时等待 |
SO_OOBINLINE | BOOL | 在普通数据流中接收带外数据 |
SO_RCVBUF | int | 设置接收数据的缓冲的大小 |
SO_REUSEADDR | BOOL | 允许套接字绑定到一个已经使用的地址上 |
SO_SNDBUF | int | 设置发送数据的缓冲的大小 |
TCP_NODELAY | BOOL | 禁止发送数据时的Nagle算法 |
SetSockOpt不支持的Berkeley软件发布(BSD)选项有:
值 | 类型 | 含义 |
SO_ACCEPTCONN | BOOL | 套接字正在监听,允许接收连接 |
SO_ERROR | int | 返回并清除错误状态 |
SO_RCVLOWAT | int | 接收最低水准标志 |
SO_RCVTIMEO | int | 接收到超时消息 |
SO_SNDLOWAT | int | 发送最低水准标志 |
SO_SNDTIMEO | int | 发送超时消息 |
SO_TYPE | int | 设置套接字的类型 |
IP_OPTIONS | int | 设置IP头上的选项字段 |
最后
以上就是傲娇西牛为你收集整理的心跳包机制(★firecat推荐★)的全部内容,希望文章能够帮你解决心跳包机制(★firecat推荐★)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复