概述
TCP是面向连接的,在实际应用中通常都需要检测对端是否还处于连接中。如果已断开连接,主要分为以下几种情况:
1. 连接的对端正常关闭,即使用closesocket关闭连接。
2. 连接的对端非正常关闭,包括对端异常关闭,网络断开等情况。
对于第一种情况,很好判断,但是对于第二种情况,可能会要麻烦一些。在网上找到了一些文章,大致有以下两种解决方法:
? 自己编写心跳包程序
简单的说也就是在自己的程序中加入一条线程,定时向对端发送数据包,查看是否有ACK,如果有则连接正常,没有的话则连接断开。
? 使用TCP的keepalive机制
这个需要在WinSock编程时对当前SOCKET进行相应设置即可,比较方便。
为了方便起见,我这里采用keepalive机制,下面我就以WinSock上我实验得到的结果来大致讲一下其机理和使用方法。
首先说一下keepalive来判断异常断开的原理,其实keepalive的原理就是TCP内嵌的一个心跳包。
以服务器端为例,如果当前server端检测到超过一定时间(默认是7,200,000 milliseconds ,也就是2个小时)没有数据传输,那么会 向client端发送一个keep-alive packet(该keep-alive packet就是ACK和当前TCP序列号减一的组合),此时client端应该为以下三种情况之一:
1. client端仍然存在,网络连接状况良好。此时client端会返回一个ACK。server端接收到ACK后重置计时器,在2小时后再发送探测。如果2小时内连接上有数据传输,那么在该时间基础上向后推延2个小时。
2. 客户端异常关闭,或是网络断开。在这两种情况下,client端都不会响应。服务器没有收到对其发出探测的响应,并且在一定时间(系统默认为1000 ms)后重复发送keep-alive packet,并且重复发送一定次数(2000 XP 2003 系统默认为5次, Vista 后的系统默认为10次)。
3. 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。(这条摘抄自http://www.cppblog.com/zhangyq/archive/2010/02/28/108615.html,我自己并不太明白)。
了解了keep alive大致的原理,下来看看在程序中怎么用,怎么设置参数:
#include <mstcpip.h>
BOOL bKeepAlive = TRUE;
int nRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,(char*)&bKeepAlive,sizeof(bKeepAlive));
if (nRet == SOCKET_ERROR)
{
TRACE(L"setsockopt failed: %d/n", WSAGetLastError());
return FALSE;
}
// set KeepAlive parameter
tcp_keepalive alive_in;
tcp_keepalive alive_out;
alive_in.keepalivetime = 500; // 0.5s
alive_in.keepaliveinterval = 1000; //1s
alive_in.onoff = TRUE;
unsigned long ulBytesReturn = 0;
nRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in),&alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);
if (nRet == SOCKET_ERROR)
{
TRACE(L"WSAIoctl failed: %d/n", WSAGetLastError());
return FALSE;
}
其中,setsockopt设置了keepalive模式,但是系统对keepalive默认的参数可能不符合我们的要求,比如空闲2小时后才探测对端是否活跃,所以WSAIoctl函数通过tcp_keepalive结构体对这些参数进行了相应设置。tcp_keepalive这个结构体在mstcpip.h头文件中有定义:
structtcp_keepalive
{
ULONGonoff ; //是否开启keepalive
ULONGkeepalivetime ; //多长时间(ms)没有数据就开始send心跳包
ULONGkeepaliveinterval ; //每隔多长时间(ms)send一个心跳包,
//发5次(2000 XP 2003默认), 10次(Vista后系统默认)
};
这个结构体设置了空闲检测时间,及检测时重复发送的间隔时间。详细的可以查询msdn : http://msdn.microsoft.com/en-us/library/dd877220(VS.85).aspx。
按照msdn上的说法,这些参数也可以通过在注册表里设置,分别为:
HKLM/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/KeepAliveTime
HKLM/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/KeepAliveInterval
另外,有些人可能已经发现了,tcp_keepalive这个结构体中没有对重试次数这个参数的设置,这个参数可以通过注册表来设置,具体位置为:
HKLM/SYSTEM/CurrentControlSet/Services/Tcpip/Parameters/TcpMaxDataRetransmissions
关于在注册表中设置这几个参数,我在XP和Server2008系统中都没有找到,msdn上说貌似只是支持server 2003,我这里没有实验,具体不太清楚。
设置好keepalive以后,我们通过实验来看看当client异常退出或是网络断掉的情况下,keepalive怎么通知我们异常断开的情况。这里采用select模式,实验环境为XP系统和Win7系统,几种情况返回值如下:
1. 正常断开
select函数正常返回,recv函数返回0
2. 异常断开
a) 程序异常退出,如client端重启,应用非正常关闭等
select函数正常返回,recv函数返回SOCKET_ERROR,WSAGetLastError()得到的结果为WSAECONNRESET(10054)。
b) 网络断开
结果同上:select函数正常返回,recv函数返回SOCKET_ERROR,WSAGetLastError()得到的结果为WSAECONNRESET(10054)。
P.S. 网上有些文章中写的WSAGetLastError()得到的结果为ETIMEDOUT,我这里不太清楚为什么和我这里得到的不太一样。
另外,在实验中,我发现了一个和以前理解的不太相同的地方,在这里也记录下来:
对于程序异常退出的情况(这里所说的异常退出包括程序异常关闭、重启等情况,但不包括系统待机休眠),实际上在不开启keepalive的情况下也是可以检测到的,我这里测试得到在不开启keepalive的情况下,异常关闭client端程序,server 端recv函数会立即返回SOCKET_ERROR,last error同样为WSAECONNRESET。但是对于网络断开及系统待机休眠的情况,则必须设置 keepalive 才能检测到,并且对于上述情况,当网络重新连接或者系统恢复后,SOCKET连接并不能恢复。
具体原因我这里也不太清楚,看到有一篇文章是这样写的:“异常关闭下,SOCKET虚拟通路会被重设,远端正在接受的调用就都会失败”。不知道正确与否,感觉有一定的道理,暂时记录下来。
最后
以上就是含蓄海燕为你收集整理的WinSock TCP keepalive的机理及使用(自动心跳包的发送)的全部内容,希望文章能够帮你解决WinSock TCP keepalive的机理及使用(自动心跳包的发送)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复