远程主机终止后,Recv() 调用挂起

Posted

技术标签:

【中文标题】远程主机终止后,Recv() 调用挂起【英文标题】:Recv() call hangs after remote host terminates 【发布时间】:2013-06-21 02:34:34 【问题描述】:

我的问题是我有一个线程在 recv() 调用中。远程主机突然终止(没有 close() 套接字调用)并且 recv() 调用继续阻塞。这显然不好,因为当我加入线程以关闭进程(本地)时,该线程将永远不会退出,因为它正在等待一个永远不会到来的 recv。

所以我的问题是人们通常认为哪种方法是处理此问题的最佳方法?在回答之前应该知道一些额外的注意事项:

我无法确保远程主机在退出前关闭套接字。

此解决方案不能使用外部库(例如 boost)。它必须使用 C++/C 的标准库/特性(最好不是 C++0x 特定的)。

我知道过去可能有人问过这个问题,但我想让别人知道如何正确地纠正这个问题(不做我过去会做的超级骇人听闻的事情)。

谢谢!

【问题讨论】:

【参考方案1】:

假设你想继续使用阻塞套接字,你可以使用SO_RCVTIMEO socket option:

   SO_RCVTIMEO and SO_SNDTIMEO
          Specify the receiving or sending  timeouts  until  reporting  an
          error.   The parameter is a struct timeval.  If an input or out-
          put function blocks for this period of time, and data  has  been
          sent  or received, the return value of that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  nonblocking.   If  the  timeout is set to zero (the default)
          then the operation will never timeout.

所以,在您开始接收之前:

struct timeval timeout =  timo_sec, timo_usec ;
int r = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
assert(r == 0); /* or something more user friendly */

如果您愿意使用非阻塞 I/O,那么您可以使用poll()select()epoll()kqueue(),或者任何适合您系统的事件调度机制。你需要使用非阻塞I/O的原因是你需要允许recv()的系统调用返回通知你socket的输入队列中没有数据。使用的例子有点复杂:

for (;;) 
    ssize_t bytes = recv(s, buf, sizeof(buf), MSG_DONTWAIT);
    if (bytes > 0)  /* ... */ continue; 
    if (bytes < 0) 
        if (errno == EWOULDBLOCK) 
            struct pollfd p =  s, POLLIN, 0 ;
            int r = poll(&p, 1, timo_msec);
            if (r == 1) continue;
            if (r == 0) 
                /*...handle timeout */
                /* either continue or break, depending on policy */
            
        
        /* ...handle errors */
        break;
    
    /* connection is closed */
    break;

【讨论】:

关于此的几个问题:无论如何,是否可以判断套接字是否实际上已使用此过程关闭?我问这个是因为在我的情况下,套接字可能会在突然使用之前打开很长时间(想想几个小时)。如果触发超时,errno 是否会设置为端点未连接?此外,此套接字选项的平台可用性是什么? 您再次调用recv() 来测试套接字是否已关闭。您可以使用MSG_DONTWAIT|MSG_PEEK 标志来检查是否有数据、没有数据或套接字是否关闭(在这种情况下会返回0)。 errno 设置为EAGAIN,因此您可以决定如何处理超时。该选项在 Linux 上公布,并在 POSIX 中定义。 非常好,如果在套接字仍然可用的情况下,recv() 将阻塞第二次调用(从您的描述中看起来是这样),这可能会很好(假设 Cray 支持这个 sockopt,它它应该因为它在标准中,但这不是保证)。谢谢。 这可能是最好的解决方案,但线程取消 (pthread_cancel) 也是一个可行的解决方案。要正确使用它,您需要设置取消清理处理程序并在取消不安全的地方阻止取消。 SO_RCVTIMEO 在大多数情况下都用于阻塞套接字,但是当服务器终止(RST)连接时,我发现客户端套接字卡在了 recv() 上。有什么想法吗?【参考方案2】:

您可以使用 TCP 保持活动探测来检测远程主机是否仍然可以访问。启用keep-alive后,如果连接空闲时间过长,操作系统会发送探测;如果远程主机没有响应探测,则连接关闭。

在 Linux 上,您可以通过设置 SO_KEEPALIVE 套接字选项来启用 keep-alive 探测,并且可以使用 TCP_KEEPCNTTCP_KEEPIDLETCP_KEEPINTVL 套接字选项配置 keep-alive 的参数。有关这些的更多信息,请参阅 tcp(7)socket(7)

Windows 还使用SO_KEEPALIVE 套接字选项来启用keep-alive 探测,但要配置keep-alive 参数,请使用SIO_KEEPALIVE_VALS ioctl。

【讨论】:

【参考方案3】:

你可以使用 select()

来自http://linux.die.net/man/2/select

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

select() 阻塞,直到一个或多个文件描述符上的第一个事件(读就绪、写就绪或异常)或发生超时。

【讨论】:

Select 是一个选项,如果出现上述情况会发生什么?我假设在这种情况下会给出“例外”正确吗? 这里的服务器示例值得回顾,可能与您需要的非常接近:gnu.org/software/libc/manual/html_node/… 我相信服务器示例解释了在客户端断开连接时,select() 在套接字上返回读取就绪,但读取将读取 0 个字节,这表示断开连接。【参考方案4】:

sockopts 和select 可能是理想的选择。您应该考虑作为备份的另一个选项是向您的进程发送信号(例如使用alarm() 调用)。这应该会强制任何正在进行的系统调用退出并将errno 设置为EINTR

【讨论】:

我确实考虑过警报()。然而,由于这是一个图书馆,我不能保证图书馆的用户没有捕捉到信号。 无论如何/是否处理信号,其传递都会导致正在进行的系统调用退出。 @BrianCain:这是不正确的。未处理的SIGALRM 将终止进程。一个被处理的可能会或可能不会中断阻塞函数,这取决于信号处理程序是在没有或有SA_RESTART 的情况下安装的。一个被忽略的信号将无济于事。由于这是库代码,它与信号处理/处理程序没有任何关系,因此这不是一种可行的方法。即使是这样,您也无法控制SIGALRM 将被传递到哪个线程,所以这也是一个令人担忧的问题。

以上是关于远程主机终止后,Recv() 调用挂起的主要内容,如果未能解决你的问题,请参考以下文章

linux 远程主机后台运行任务 挂起脚本

USB远程唤醒

Psycopg2 db连接在丢失的网络连接上挂起

win10vpn连接服务器失败提示:“连接被远程计算机终止”怎么办?

Zabbix设置触发器调用远程主机脚本实现触发告警后自动启动自愈功能

断路器(Curcuit Breaker)模式