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

时间:2013-06-21 02:34:34

标签: c++ c sockets

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

所以我的问题是人们通常认为哪种方法是解决这个问题的最佳方法?在回答之前还应该知道一些值得注意的事项:

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

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

我知道这可能在过去曾被问过,但是我想知道如何正确地纠正这个问题(没有做过我过去会做的超级hacky)。

谢谢!

4 个答案:

答案 0 :(得分:5)

假设您要继续使用阻塞套接字,可以使用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()的系统调用返回以通知您套接字的输入队列中没有数据。使用的示例涉及更多:

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;
}

答案 1 :(得分:2)

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

在Linux上,您可以通过设置SO_KEEPALIVE套接字选项来启用保持活动探测,并且可以使用TCP_KEEPCNTTCP_KEEPIDLE配置保持活动的参数,以及TCP_KEEPINTVL套接字选项。有关这些内容的详情,请参阅tcp(7)socket(7)

Windows还使用SO_KEEPALIVE套接字选项启用保持活动探测,但是要配置保持活动参数,请使用SIO_KEEPALIVE_VALS ioctl

答案 2 :(得分:0)

您可以使用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()阻塞直到第一个事件(读取就绪,写入就绪或异常)在一个或多个文件描述符上或发生超时。

答案 3 :(得分:0)

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