部分信息可以看UNIX网络编程第157页,
摘录过来:
在一个正常的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操作的一方就会因为没有任何连接中断的通知而一直等待下去,也就是会被长时间卡住。这种情形解决的办法:
1>自己编写心跳包程序
简单的说也就是在自己的程序中加入一条线程,定时向对端发送数据包,查看是否有ACK,如果有则连接正常,没有的话则连接断开
2>启动TCP编程里的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. 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。
对于实用程序来说,2小时的空闲时间太长。因此,我们需要手工开启Keepalive功能并设置合理的Keepalive参数。在XP和WIN2003系统上,可以针对单独的socket来设置,但是在windows 2000,不能单独设置,如果设置,那么影响是整个系统的所有socket。
了解了keep alive大致的原理,下来看看在程序中怎么用,怎么设置参数:
#include <mstcpip.h> BOOL bKeepAlive = TRUE; int nRet = setsockopt(m_socket, 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(m_socket, 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头文件中有定义:
struct tcp_keepalive { ULONG onoff; //是否开启keepalive ULONG keepalivetime; //多长时间(ms)没有数据就开始send心跳包 ULONG keepaliveinterval; //每隔多长时间(ms)send一个心跳包, //发5次(2000 XP 2003默认), 10次(Vista后系统默认) };
这个结构体设置了空闲检测时间,及检测时重复发送的间隔时间
按照msdn上的说法,这些参数也可以通过在注册表里设置,分别为:
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveInterval
另外,有些人可能已经发现了,tcp_keepalive这个结构体中没有对重试次数这个参数的设置,这个参数可以通过注册表来设置,具体位置为:
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpMaxDataRetransmissions
此处的keepalivetime表示的是TCP连接处于畅通时候的探测频率,一旦探测包没有返回,就以keepaliveinterval的频率发送,经过若干次的重试,如果探测包都没有返回,那么就得出结论:TCP连接已经断开,于是上面的Recv或Send调用也就能马上返回,不会无限制地卡住了。
上图是对上面文字的说明。亮条之前,TCP处于畅通状态,KeepAlive是以1000毫秒(keepalivetime的值)的频率发送探测包,在发送到第32个探测包的时候,探测包没有返回,于是就以5000毫秒(keepalivetime的值)的频率发送探测包,重发几次后,探测包都没有返回,于是就得出结论:此TCP连接已经断开了!
设置好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)。
对于程序异常退出的情况,实际上在不开启keepalive的情况下也是可以检测到的