使用keepalive检测客户端非正常断开的问题

pomoq 2010-08-09 12:36:31
小弟想让服务端检测与客户端的TCP连接是否已经非正常断开(如网线断开或客户端所在机子突然断电),参考网上的资料小弟在服务端开启keepalive来检测,客户端没有开启keepalive。

服务端的部分代码如下:
int set_keepalive(SOCKET s, TCP_KEEPALIVE *pTCP_KeepAlive)
{
int res = 0;

BOOL bKeepAlive = TRUE;
int keepalive = 1;

TCP_KEEPALIVE inKeepAlive = {0}; //输入参数
unsigned long ulInLen = sizeof(TCP_KEEPALIVE);
TCP_KEEPALIVE outKeepAlive = {0}; //输出参数
unsigned long ulOutLen = sizeof(TCP_KEEPALIVE);

unsigned long ulBytesReturn = 0;

unsigned long param=1;

//res = ioctlsocket(s,FIONBIO,¶m);

//设置KeepAlive
res = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeepAlive, sizeof(bKeepAlive));

if (res == 0) {
//设置KeepAlive检测时间和次数
//设置socket的keep alive为10秒,并且发送次数为3次
inKeepAlive.onoff = pTCP_KeepAlive->onoff;
inKeepAlive.keepaliveinterval = pTCP_KeepAlive->keepaliveinterval; //两次KeepAlive探测间的时间间隔
inKeepAlive.keepalivetime = pTCP_KeepAlive->keepalivetime; //开始首次KeepAlive探测前的TCP空闭时间

res = WSAIoctl(s,
SIO_KEEPALIVE_VALS,
(LPVOID)&inKeepAlive,
ulInLen,
(LPVOID)&outKeepAlive,
ulOutLen,
&ulBytesReturn,
NULL,
NULL);
}

return res;
}

//接收线程
ULONG __stdcall _Receive_Thread(LPVOID args)
{
int res;
NETCTX *pNetCtx= (NETCTX *)args;

char buf[MAX_RECEIVE_BUFF_SIZE];

fd_set fdread;

struct timeval timeout;

TCP_KEEPALIVE stKeepAlive = {0};
stKeepAlive.onoff = 1;
stKeepAlive.keepaliveinterval = 5000;
stKeepAlive.keepalivetime = 1000;

set_keepalive(pNetCtx->s, &stKeepAlive);

while (1){
FD_ZERO(&fdread);
FD_SET(pNetCtx->s, &fdread);

timeout.tv_sec = 1;
timeout.tv_usec = 0;

__try {
if ((res = select(0, &fdread, NULL, NULL, &timeout)) == SOCKET_ERROR) {
……
break;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
……
}

if (res > 0) {
res = recv(pNetCtx->s, buf, sizeof(buf), 0);
if (res > 0) {
//处理客户端数据
……
}
else {
……
break;
}

……
}
else{
break;
}

……
}

shutdown(pNetCtx->s, SD_BOTH);
closesocket(pNetCtx->s);
……

return 0;
}

// 连接侦听线程
ULONG __stdcall _ListenThread(LPVOID args)
{
int res = 0;
NETCTX *pNetCtx= (NETCTX *)args;
fd_set fdread;
int addr_len;

struct timeval timeout;

SOCKET s;
NETCTX *pRcvNetCtx;

DWORD dwThreadID = 0;

for (;;) {

FD_ZERO(&fdread);
FD_SET(pNetCtx->s, &fdread);

timeout.tv_sec = 1;
timeout.tv_usec = 0;

if ((res = select(0, &fdread, NULL, NULL, &timeout)) == SOCKET_ERROR)
{
……
break;
}

if (res > 0) {
s = accept(pNetCtx->s, &remoteaddr, &addr_len);

if (s != INVALID_SOCKET) {
pRcvNetCtx =(NETCTX *)malloc(sizeof(NETCTX));
pRcvNetCtx->s = s;
……
recvctx->hThread = CreateThread(NULL,0,_Receive_Thread, pRcvNetCtx,0,&dwThreadID);
……
}
else{
closesocket(s);
}
}
……
}

shutdown(pNetCtx->s, SD_BOTH);
closesocket(pNetCtx->s);
}

客户端以tcp方式连接上服务端后,通过抓包发现,服务端定时向客户端发送keepalive数据包,客户端也返回了响应包。
然后小弟把客户端所在机子的网线拔掉,大约5秒钟过后,服务端不再向客户端发送keepalive数据包。
然而服务端直到大约20秒钟后,因为recv返回-1而得知socket无效,并不是在停止keepalive检测那一刻就得知socket无效的。

而且,上面说的“客户端异常断开,服务端约20秒后就发现socjet无效”,并不是因为设置了keepalive才会20秒后发现,即使服务端不开启keepalive,一样在20秒后发现socket无效。如果客户端的并发量很大的话,那就不止20秒了,还会更长久。

所以很困惑,感觉开启keepalive似乎没有效果。


各位大侠请指点一下,看小弟的代码是否有缺陷修正的,或者有什么更好的办法检测异常断开,除了自己去实现心跳。
呵呵,小弟分不多,请体谅!!
...全文
331 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
jwybobo2007 2010-09-26
  • 打赏
  • 举报
回复
如果不设置,并且你自己没有发送另外的数据包的话,服务端可能在2个小时后才能知道客户端异常断开
如果设置,就会在一定时候内,通知你socket无效,但这个时间并不完全是你用WSAIoctl设置的时间,它只是表示间隔多少时间发,尝试发几次
Eleven 2010-08-09
  • 打赏
  • 举报
回复
一般是用心跳包来实现

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧