回收TCP毗连的C/S模式软件,毗连的两边在毗连空闲状态时,假如任意一方意外瓦解、当机、网线断开或路由器妨碍,另一方无法得知TCP毗连已经失效,除非继承在此毗连上发送数据导致错误返回。许多时候,这不是我们需要的。我们但愿处事器端和客户端都能实时有效地检测到毗连失效,然后优雅地完成一些清理事情并把错误陈诉给用户。
如何实时有效地检测到一方的非正常断开,一直有两种技能可以运用。一种是由TCP协议层实现的Keepalive,另一种是由应用层本身实现的心跳包。
TCP默认并不开启Keepalive成果,因为开启Keepalive成果需要耗损特另外宽带和流量,尽量这微不敷道,但在按流量计费的情况下增加了用度,另一方面,Keepalive配置不公道时大概会因为短暂的网络颠簸而断开康健的TCP毗连。而且,默认的Keepalive超时需要7,200,000 milliseconds,即2小时,探测次数为5次。
对付Win2K/XP/2003,可以从下面的注册表项找到影响整个系统所有毗连的keepalive参数:
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters]
"KeepAliveTime”=dword:006ddd00
"KeepAliveInterval"=dword:000003e8
"MaxDataRetries"="5"
对付实用的措施来说,2小时的空闲时间太长。因此,我们需要手工开启Keepalive成果并配置公道的Keepalive参数。
// 开启KeepAlive
BOOL bKeepAlive = TRUE;
int nRet = ::setsockopt(socket_handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));
if (nRet == SOCKET_ERROR)
{
return FALSE;
}
// 配置KeepAlive参数
tcp_keepalive alive_in = {0};
tcp_keepalive alive_out = {0};
alive_in.keepalivetime = 5000; // 开始首次KeepAlive探测前的TCP空闭时间
alive_in.keepaliveinterval = 1000; // 两次KeepAlive探测间的时距离断
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);
if (nRet == SOCKET_ERROR)
{
return FALSE;
}
开启Keepalive选项之后,对付利用IOCP模子的处事器端措施来说,一旦检测到毗连断开,GetQueuedCompletionStatus函数将当即返回FALSE,使得处事器端能实时排除该毗连、释放该毗连相关的资源。对付利用select模子的客户端来说,毗连断开被探测到时,以recv目标阻塞在socket上的select要领将当即返回SOCKET_ERROR,从而得知毗连已失效,客户端措施便有时机实时执行排除事情、提醒用户或从头毗连。
另一种技能,由应用措施本身发送心跳包来检测毗连的康健性。客户端可以在一个Timer中或初级此外线程中按时向发处事器发送一个短小精壮的包,并期待处事器的回应。客户端措施在一按时间内没有收随处事器回应即认为毗连不行用,同样,处事器在一按时间内没有收到客户端的心跳包则认为客户端已经掉线。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
windows下此处的”非正常断开”指TCP毗连不是以优雅的方法断开,如网线妨碍等物理链路的原因,尚有溘然主机断电等原因.
有两种要领可以检测:
1.TCP毗连两边按时发握手动静
2.操作TCP协议栈中的KeepAlive探测
第二种要领简朴靠得住,只需对TCP毗连两个Socket设定KeepAlive探测,
所以本文只讲第二种要领在Linux,Window2000下的实现(在其它的平台上没有作进一步的测试)
Windows 2000平台下 头文件
#include <mstcpip.h>
//界说布局及宏
/*
struct TCP_KEEPALIVE {
u_longonoff;
u_longkeepalivetime;
u_longkeepaliveinterval;
} ;
*/
tcp_keepalive live,liveout;
live.keepaliveinterval=5000; //每5秒发一次探测报文,发5次没有回应,就断开
live.keepalivetime=30000;//高出30s没有数据,就发送控测包
live.onoff=TRUE;
int Opt = 1;
int iRet = setsockopt(Accept,SOL_SOCKET,SO_KEEPALIVE,(char *)&Opt,sizeof(int));
if(iRet == 0){
DWORD dw;
if(::WSAIoctl(Accept,SIO_KEEPALIVE_VALS,
&live,sizeof(live),&liveout,sizeof(liveout),
&dw,NULL,NULL)== SOCKET_ERROR){
}
}
ACE下代码
//by rainfish blog.csdn.net/bat603
int Opt = 1;
//在测试进程中,发明检测的次数是5次,即下面的配置中,从最近一次动静开始计较的10秒后,每次隔断5秒,持续发送5次,即35秒发明网络断了
tcp_keepalive live,liveout;
live.keepaliveinterval=5000; //每次检测的隔断 (单元毫秒)
live.keepalivetime=10000; //第一次开始发送的时间(单元毫秒)
live.onoff=TRUE;
int iRet = stream.set_option(SOL_SOCKET,SO_KEEPALIVE,&Opt,sizeof(int));
if(iRet == 0){
DWORD dw;
//此处显示了在ACE下获取套接字的要领,即句柄的(SOCKET)化就是句柄
if(WSAIoctl((SOCKET)h,SIO_KEEPALIVE_VALS,&live,sizeof(live),
&liveout,sizeof(liveout),&dw,NULL,NULL)== SOCKET_ERROR){
//Delete Client
return;
}
}
#p#分页标题#e#
Linux平台下
#include "/usr/include/linux/tcp.h" #include "/usr/include/linux/socket.h" ////KeepAlive实现,单元秒 //下面代码要求有ACE,假如没有包括ACE,则请把用到的ACE函数改成linux相应的接口 int keepAlive = 1;//设定KeepAlive int keepIdle = 5;//开始首次KeepAlive探测前的TCP空闭时间 int keepInterval = 5;//两次KeepAlive探测间的时距离断 int keepCount = 3;//鉴定断开前的KeepAlive探测次数 if(setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(void*)&keepAlive,sizeof(keepAlive)) == -1) { ACE_DEBUG ((LM_INFO, ACE_TEXT ("(%P|%t) setsockopt SO_KEEPALIVE error!/n"))); } if(setsockopt(s,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle)) == -1) { ACE_DEBUG ((LM_INFO, ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPIDLE error!/n"))); } if(setsockopt(s,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval)) == -1) { ACE_DEBUG ((LM_INFO, ACE_TEXT ("(%P|%t) setsockopt TCP_KEEPINTVL error!/n"))); } if(setsockopt(s,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount)) == -1) { ACE_DEBUG ((LM_INFO, ACE_TEXT ("(%P|%t)setsockopt TCP_KEEPCNT error!/n"))); }
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++