使用TCP Keep-alive与TCP_USER_TIMEOUT机制判断通信对端是否存活

最近在做RDP协议代理的时候被测试提了一个Bug,Bug说的是当通信中的服务器网线被拔了以后,代理程序不能检测到对端已经不可用,不能正常退出。我的第一反应是调用send的时候为什么没有返回-1呢?接下来的测试让我重新认识了send。

正常通信的情况下,send函数发送成功会返回发送数据的字节数。当有错误发生时,send返回-1,全局变量errno被设置。很多情况下,send返回-1是由于连接被对端关闭(对端发送了RST或者FIN包),这种情况errno会被设置为ECONNRESET(Connection reset by peer)。

可是在对端的网线被拔、网卡被卸载或者禁用的时候,对端没有机会向本地操作系统发送TCP RST或者FIN包来关闭连接。这时候操作系统不会认为对端已经挂了。所以在调用send函数的时候,返回的仍然是我们指定发送的数据字节数。当我们无法通过send的返回值来判断对端是否存活的情况下,就要使用TCP Keep-alive机制了。

在《Unix网络编程(卷一)》中提到,使用SO_KEEPALIVE套接字选项启用对套接字的保活(Keep-alive)机制。

给一个TCP套接口设置保持存活(keepalive)选项后,如果在2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方发一个保持存活探测分节(keepalive probe)。

TCP提供了这种机制帮我们判断对端是否存活,如果对端没有对KeepAlive包进行正常的响应,则会导致下一次对套接字的send或者recv出错。应用程序就可以检测到这个异常。

设置KeepAlive机制的选项如下代码所示:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int keep_alive = 1;
int keep_idle = 5, keep_interval = 1, keep_count = 3;
int ret = 0;
 
if (-1 == (ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive,
     sizeof (keep_alive)))) {
     fprintf (stderr, "[%s %d] set socket to keep alive error: %s" , __FILE__,
         __LINE__, ERRSTR);
}
if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle,
     sizeof (keep_idle)))) {
     fprintf (stderr, "[%s %d] set socket keep alive idle error: %s" , __FILE__,
         __LINE__, ERRSTR);
}
if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keep_interval,
     sizeof (keep_interval)))) {
     fprintf (stderr, "[%s %d] set socket keep alive interval error: %s" , __FILE__,
         __LINE__, ERRSTR);
}
if (-1 == (ret = setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keep_count,
     sizeof (keep_count)))) {
     fprintf (stderr, "[%s %d] set socket keep alive count error: %s" , __FILE__,
         __LINE__, ERRSTR);
}
  1. 设置SO_KEEPALIVE选项,将这个选项设置为1,代表打开KeepAlive机制。
  2. 设置TCP_KEEPIDLE选项,值为5秒,代表如果TCP连接上有五秒钟没有任何数据包传输,则启动保活机制,发送TCP Keep-alive机制。默认为2小时。
  3. 设置TCP_KEEPINTVL选项,值为1秒,代表如果启动保活机制,则每隔1秒发送一个Keep-alive包。默认为75秒。
  4. 设置TCP_KEEPCNT选项,值为3,代表如果对端对3次Keep-alive数据包都没有正常响应,则判断对端已经崩溃。默认为9。

在设置了这些选项后,可以看到当对端断网5秒后,本地程序发送三个Keep-alive包后发送RST包断开连接,如下图:

keep-alive

这样断网的判断问题就解决了。:-)

——————-2013年11月8日——————-

在实验的过程中发现,如果发送方发送的数据包没有收到接收方回复的ACK数据包,则TCP Keep-alive机制就不会被启动,而TCP会启动超时重传机制,这样就使得TCP Keep-alive机制在未收到ACK包时失效。在查阅这个问题时找到了stackoverflow上面的资料:http://stackoverflow.com/questions/5907527/application-control-of-tcp-retransmission-on-linux

根据排名第一的回答表示,Linux Kernel 2.6.37中增加了一个叫做TCP_USER_TIMEOUT的socket选项。答案大意是,TCP_USER_TIMEOUT选项是TCP层的socket选项,选项接受unsigned int类型的值。值为数据包被发送后未接收到ACK确认的最大时长,以毫秒为单位,例如设置为10000时,代表如果发送出去的数据包在十秒内未收到ACK确认,则下一次调用send或者recv,则函数会返回-1,errno设置为ETIMEOUT,代表connection timeout。

实现的代码应该如下:


1
2
3
4
unsigned int timeout = 10000;
if (-1 == setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof (timeout))) {
     fprintf (stderror, "set TCP_USER_TIMEOUT option error: %s" , strerror ( errno ));
}

如上所述,使用TCP Keep-alive加上TCP_USER_TIMEOUT机制,就可以完美解决通信对端异常断网、掉电的情况下,连接被长期挂起的问题了。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TCP Keep-alive是一种机制,用于检测并恢复网络连接中存在的故障情况。当两个系统之间的网络连接长时间没有数据交换,TCP协议可能会认为该连接已断开,并关闭连接以防资源浪费。然而,在某些情况下,如Web服务器、长连接应用等,连接应该保持开放状态而不仅仅是因为无活动就关闭。 ### TCP Keep-alive的工作原理 TCP Keep-alive通过发送空的数据包来检查连接的状态。发送间隔由`TIME_WAIT`定时器控制,默认设置下这个间隔大约为7200秒(两小时)。如果在这个时间周期内接收端没有接收到任何来自对端的数据包,那么接收端会自动向源端发送一个ICMP消息,提示对方其连接已经超时。同时,接收端将更新其本地的超时计数器,准备在未来的时间周期再次尝试连通。 ### 实现TCP Keep-alive的方式 #### 服务器端: 对于服务器端,通常不需要显式配置TCP Keep-alive,因为在Linux和其他Unix系统上默认已经启用了此功能。但为了确保TCP Keep-alive能正常工作,可以调整相关的设置,例如增加`TIME_WAIT`超时时间或者更改接收数据的等待时间。 #### 客户端: 客户端需要更直接地与服务器协商开启TCP Keep-alive。这通常在建立连接的握手过程中进行,可以通过HTTP请求头或者其他特定于应用层的消息来实现。例如,在HTTP协议中,可以使用特定的HTTP版本或者自定义头部信息来触发Keep-alive机制。 ### 应用场景 TCP Keep-alive特别适用于需要长时间保持连接的应用,比如Web服务、文件传输协议(FTP)、远程桌面协议(RDP)以及其他任何依赖于持续连接的应用程序。它有助于提高系统的整体可用性和用户体验,避免因偶然的网络波动导致的意外断开连接。 ### 相关问题: 1. **TCP Keep-alive的配置方法是什么?** - 对于服务器,可以在运行时通过命令行参数或者配置文件来调整`TIME_WAIT`超时时间; - 对于客户端,需要根据使用的应用程序或协议文档来了解如何启用或配置TCP Keep-alive。 2. **TCP Keep-alive在哪些情况下可能导致问题?** - 过短的`TIME_WAIT`超时时间可能会导致频繁的连接尝试; - 过长的超时时间则可能影响资源占用以及系统的响应速度。 3. **是否所有的网络环境下都适合使用TCP Keep-alive?** - 不一定。在高带宽、低延迟环境中,TCP Keep-alive可能是有益的;但在流量密集型网络或者对网络资源非常敏感的情况下,过多的Keep-alive操作可能会带来额外的网络负担。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值