Socket,检测连接是否丢失

15

我正在使用TCP连接连接服务器进程和客户端进程,我必须检测两台机器之间的物理连接是否中断。我试图使用keepalive来实现这一点,将默认的系统范围值降低到:

TCP_KEEPIDLE=5
TCP_KEEPCNT=5
TCP_KEEPINTVL=1

当连接断开时(我拔掉电缆),只有服务器在10秒内检测到连接已经丢失,客户端一直挂起。

这是客户端代码:

#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/tcp.h>

int main(int argc, char** argv) {
  char myVector[1600];

  int mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  if (mySocket < 0 ) {
    std::cout << "error creating the socket" << strerror(errno) << std::endl;
    ::exit(-1);
 }

 struct sockaddr_in sin;
 memset( (char *)&sin, 0, sizeof( sin ) );
 sin.sin_addr.s_addr = inet_addr("192.168.21.27");
 sin.sin_port   = htons(7788);
 sin.sin_family = AF_INET;

 if ( connect( mySocket, (struct sockaddr *)&sin, sizeof( sin )) < 0 ) {
   std::cout << "Error on connection: " << strerror(errno) << std::endl;
   ::exit(-1);
 }

 int optval = 1;
 socklen_t optlen = sizeof(optval);

 /*Enabling keep alive*/
 if(setsockopt(mySocket, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
   std::cout << "Error setting SO_KEEPALIVE: " << strerror(errno) << std::endl;
 }

 optval = 5;
 optlen = sizeof(optval);
 if(setsockopt(mySocket, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) {
    std::cout << "Error setting TCP_KEEPIDLE: " << strerror(errno) << std::endl;
 }

 optval = 5;
 optlen = sizeof(optval);
 if(setsockopt(mySocket, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < 0) {
   std::cout << "Error setting TCP_KEEPCNT: " << strerror(errno) << std::endl;
 }

 optval = 1;
 optlen = sizeof(optval);
 if(setsockopt(mySocket, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) {
   std::cout << "Error setting TCP_KEEPINTVL: " << strerror(errno) << std::endl;
 }

 for (;;) {
   ssize_t myRet= ::send(mySocket,
                                      myVector,
                                      sizeof(myVector),
                                     0);
   if (myRet < 0) {
     std::cout << "Error: " << strerror(errno) << std::endl;
     break;
   }
   std::cout << myRet << "."; std::cout.flush();
   sleep(1);
 }
}

我肯定是错过了什么,但是是什么?

5个回答

9

TCP Keepalive不适用于此类情况。

如果您想在应用层检测故障,请像SSH、IMAP和IRC等协议那样,实现一个回声/ ping类型的应用层消息。定期发送它们,如果您没有及时得到回复,则可以假定连接已断开。


嗯,那么它的用途是什么呢? - vines
1
@vines:它允许长时间存在的陈旧连接最终被检测并清除 - 这些情况下,及时性并不重要。 - caf

3
我们公司不久前曾思考过这个问题:“如何检测连接是否中断?”为了可靠地解决这个问题,我们必须实现一个“心跳”系统,即客户端定期检查(在我们的情况下每秒一次)服务器是否还在运行,并进行伪ping。如果您不想这样做,您可以等待操作系统实际检测到连接中断,但是不要指望它是可靠的...

1
@Gaetano,我很惊讶你竟然让保持连接机制工作了,即使是对于服务器而言。正如Mikarnage所指出的那样,心跳系统是所有平台和IP堆栈实现中唯一真正可靠的机制(至少在我所知道的范围内)。 - AlastairG

3
所以,经过进一步调查,即使 "TCP Keepalive" 不是用于此目的,我发现保持活动探测会在 "空闲连接" 上开始发送。问题是:什么情况下才被认为是空闲状态? 当没有数据正在传输时,连接被认为是空闲的。因此,如果两个对等方中的一个在 send(...) 上被阻塞,则实际上某些数据正在传输,连接不被视为空闲。我想我现在唯一的选择是使用带有超时的 sends/recv 进行 ping/pong,并在这些计时器到期时声明连接已 "丢失"。

我不是完全确定,但我认为这行代码(在Linux环境中)解释了为什么在使用send()时keepalive无法工作:tcp_timer.c - Gooseman

0

0
Gaetano,在我看来,TCP keep-alives 可以用于检测死连接。在你的例子中,客户端实际上可能会在发送等待中挂起,等待 TCP 重试耗尽。根据退避算法和 TCP 栈状态机的不同,这可能会持续几分钟,没有任何 keep-alive 探测,因此无法耗尽 keepcnt。
我假设服务器大部分时间都处于读取阻塞状态,在这种情况下,它的 keep-alives 将每隔 keepidle/slowhz 秒(slowhz 通常是 2 而不是 1)发送一次,并且它将很快检测到连接丢失。
如果你使用 tcpdump 捕获数据包跟踪,你将看到确切发生了什么。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接