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



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

所以我的问题是,人们通常认为处理这个问题的最好方法是什么?在回答问题之前,还需要知道一些额外的注意事项:
  • 我无法确保远程主机在退出之前关闭套接字。

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

我知道过去可能有人问过这个问题,但我希望有人能告诉我如何正确地纠正这个问题(而不是做一些我过去会做的超级黑客的事情)。

谢谢!

假设您想继续使用阻塞套接字,您可以使用SO_RCVTIMEO套接字选项:

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

您可以使用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.

可以使用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()阻塞,直到一个或多个文件描述符上的第一个事件(读准备好、写准备好或异常)或超时发生。

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

最新更新