TCP keep-alive的原理与使用

本文详细介绍了TCP协议中KeepAlive机制的工作原理及其在WinSock编程中的应用。KeepAlive机制能够帮助检测TCP连接是否正常,特别是在对端异常关闭或网络断开的情况下。文中提供了设置KeepAlive参数的具体代码示例,并解释了如何通过recv函数和WSAGetLastError来判断连接状态。

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 大致的原理,下来看看在程序中怎么用,怎么设置参数:  

 

  1. #include <mstcpip.h>  
  2.        BOOL bKeepAlive = TRUE;  
  3. int nRet = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,   
  4.                              (char*)&bKeepAlive, sizeof(bKeepAlive));  
  5. if (nRet == SOCKET_ERROR)  
  6. {  
  7.     TRACE(L"setsockopt failed: %d/n", WSAGetLastError());  
  8.     return FALSE;  
  9. }  
  10. // set KeepAlive parameter  
  11. tcp_keepalive alive_in;  
  12. tcp_keepalive alive_out;  
  13. alive_in.keepalivetime    = 500;  // 0.5s  
  14. alive_in.keepaliveinterval  = 1000; //1s  
  15. alive_in.onoff                       = TRUE;  
  16. unsigned long ulBytesReturn = 0;  
  17. nRet = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &alive_in, sizeof(alive_in),  
  18.                   &alive_out, sizeof(alive_out), &ulBytesReturn, NULL, NULL);  
  19. if (nRet == SOCKET_ERROR)  
  20. {  
  21.     TRACE(L"WSAIoctl failed: %d/n", WSAGetLastError());  
  22.     return FALSE;  
  23. }  
  24.    

其中, 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 : 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_ERRORlast error 同样 为 WSAECONNRESET 。但是对于网络断开及系统待机休眠的情况,则必须设置keepalive才能检测到,并且对于上述情况,当网络重新连接或者系统恢复后,SOCKET连接并不能恢复。

具体原因我这里也不太清楚,看到有一篇文章是这样写的:“异常关闭下, SOCKET 虚拟通路会被重设,远端正在接受的调用就都会失败”。不知道正确与否,感觉有一定的道理,暂时记录下来。

TCP Keep-AliveACK 机制是 TCP 协议中用于检测连接是否仍然有效的一种机制。其工作原理是在一段时间内没有数据交换的情况下,主动发送探测包(keep-alive probe)以确认对端是否仍然在线。如果接收方正常响应了 ACK,则认为连接仍然有效;如果没有响应或连续多次未收到 ACK,则认为连接已经断开。 在 TCP Keep-Alive 启用的情况下,发送第一个探测包之前会等待一段较长的空闲时间,通常为 2 小时(7200 秒),之后每隔一定时间(如 75 秒)发送一次探测包,直到收到响应或达到最大重试次数[^4]。 TCP Keep-AliveACK 机制可以通过系统级参数进行配置,也可以在应用程序中通过 `setsockopt` 函数设置 `SO_KEEPALIVE` 选项来启用。例如在 Linux 或 Windows 上的 socket 编程中,可以通过如下方式启用: ```c int enable = 1; setsockopt(socket_fd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); ``` 此外,还可以通过设置 TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT 等参数来调整探测包的发送间隔和重试次数[^2]。 在 Windows 系统中,TCP Keep-Alive 的行为可以通过注册表进行配置,相关键值位于 `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters` 下,包括 `KeepAliveTime`(空闲时间)、`KeepAliveInterval`(探测包发送间隔)和 `TcpMaxDataRetransmissions`(最大重传次数)等参数。 在 CANoe 自带的 TCP/IP 协议栈中,其 TCP Keep-Alive 的实现方式操作系统内核的机制类似,但具体参数和行为取决于 CANoe 的网络模块配置,通常可以通过其 API 或配置界面进行调整[^1]。 需要注意的是,TCP Keep-Alive 机制并不适用于所有场景,例如在数据通信频繁的情况下,Keep-Alive 探测包可能不会被触发,而在某些网络环境下,Keep-Alive 探测包可能会被中间设备丢弃或延迟,导致误判连接状态。此外,如果在发送数据后未收到 ACKTCP 会优先启动超时重传机制,而不是 Keep-Alive 探测机制[^3]。 ### TCP Keep-Alive HTTP Keep-Alive 的区别 虽然名称相似,但 TCP Keep-Alive 和 HTTP Keep-Alive 是两个不同的概念。TCP Keep-Alive 是传输层机制,用于维持 TCP 连接的活跃状态;而 HTTP Keep-Alive 是应用层机制,用于在一次 HTTP 请求/响应完成后保持 TCP 连接打开,以便后续请求复用该连接,减少连接建立的开销[^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值