C++ Linux下socket accept()在关闭后仍然阻塞

11

我有一个线程,用于监听新连接。

new_fd = accept(Listen_fd, (struct sockaddr *) & their_addr, &sin_size);

还有一个线程,当关闭程序时会关闭Listen_fd。 然而,在关闭Listen_fd之后,它仍然阻塞。 当我使用GDB尝试调试accept()不阻塞。 我认为这可能是SO_LINGER的问题,但默认情况下不应该打开它,并且在使用GDB时不应更改它。 有任何想法是怎么回事,或者有任何其他建议用于关闭列表套接字吗?

5个回答

7
使用:sock.shutdown(socket.SHUT_RD) 然后accept将返回EINVAL。不需要丑陋的跨线程信号!
来自Python文档: “注意close()释放与连接关联的资源,但不一定立即关闭连接。如果您想及时关闭连接,请在close()之前调用shutdown()。”

http://docs.python.org/3/library/socket.html#socket.socket.close

我多年前在使用C语言编程时遇到了这个问题,但是今天在使用Python时再次遇到同样的问题,考虑使用信号(呕吐!),然后想起了有关shutdown的注释,终于找到了解决方案!
至于那些说你不应该在线程之间关闭/使用套接字的评论...在CPython中,全局解释器锁应该保护您(假设您使用文件对象而不是原始整数文件描述符)。
以下是示例代码:
import socket, threading, time

sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind (('', 8000))
sock.listen (5)

def child ():
  print ('child  accept ...')
  try:  sock.accept ()
  except OSError as exc :  print ('child  exception  %s' % exc)
  print ('child  exit')

threading.Thread ( target = child ).start ()

time.sleep (1)
print ('main   shutdown')
sock.shutdown (socket.SHUT_RD)

time.sleep (1)
print ('main   close')
sock.close ()

time.sleep (1)
print ('main   exit')

4
我有一个C++程序,在主线程中打开一个监听套接字并生成一个子线程来接受传入连接。当接收到信号时,主线程需要关闭接受线程。close(s)无效,因为accept(…)在关闭套接字后继续阻塞,但是shutdown(s, SHUT_RD)会使accept(…)立即返回错误码EINVAL。这是完美的解决方案,即使在接受器线程在关闭发生时处于accept(…)调用之间也可以正常工作。谢谢! - Matt Whitlock
有关C++代码示例,请参见此答案:https://dev59.com/BGgu5IYBdhLWcg3wnoaC#62356967 - Alex
我看到了几个关闭另一个线程的套接字的答案。这个方法非常干净,而且不使用轮询!非常感谢! - Eric Reed

6
当在非有效的套接字FD上调用accept时,其行为是未定义的。 "非有效的套接字FD"包括曾经有效但已被关闭的数字。你可能会说:"但是Borealid,它应该返回EINVAL!", 但这并不保证-例如,在您的closeaccept调用之间,相同的FD号可能会重新分配给不同的套接字。

因此,即使您隔离并更正了导致尝试在关闭的套接字上进行accept连接的错误,您仍然可能在将来再次失败。不要这样做-纠正导致您尝试在关闭的套接字上进行accept连接的错误。

如果您的意思是先前对accept的调用在close之后继续阻塞,那么您应该向在accept中阻塞的线程发送信号。这将为其提供EINTR,并且它可以干净地退出-然后关闭套接字。不要从使用它的线程以外的线程关闭它。


我的猜测是最后一种情况。线程1在accept上阻塞。线程2关闭了套接字。线程1永远阻塞。 - Duck
这是正确的。请注意,您可能需要为使用的信号安装一个空信号处理程序。 - caf
1
哦,POSIX 的这部分真的很糟糕。 - thodg

4

shutdown()函数可能是您需要的。调用shutdown(Listen_fd, SHUT_RDWR)将导致任何被阻塞的accept()调用返回EINVAL。将shutdown()调用与原子标志的使用相结合,可以帮助确定EINVAL的原因。

例如,如果您有这个标志:

std::atomic<bool> safe_shutdown(false);

然后,您可以通过以下方式指示另一个线程停止监听:

shutdown_handler([&]() {
  safe_shutdown = true;
  shutdown(Listen_fd, SHUT_RDWR);
});

为了完整起见,这里介绍一下你的线程如何调用accept:

while (true) {
  sockaddr_in clientAddr = {0};
  socklen_t clientAddrSize = sizeof(clientAddr);
  int connSd = accept(Listen_fd, (sockaddr *)&clientAddr, &clientAddrSize);
  if (connSd < 0) {
    // If shutdown_handler() was called, then exit gracefully
    if (errno == EINVAL && safe_shutdown)
      break;
    // Otherwise, it's an unrecoverable error
    std::terminate();
  }
  char clientname[1024];
  std::cout << "Connected to "
            << inet_ntop(AF_INET, &clientAddr.sin_addr, clientname,
                         sizeof(clientname))
            << std::endl;
  service_connection(connSd);
}

1

这是一个解决方法,但你可以使用超时的方式在Listen_fd上进行select,如果发生超时,则检查是否要关闭程序。如果是,则退出循环,否则返回步骤1并执行下一个select


0
你有检查close的返回值吗? 从Linux manpages(http://www.kernel.org/doc/man-pages/online/pages/man2/close.2.html)中可以看到: “在其他线程中可能正在使用系统调用的文件描述符关闭它可能是不明智的。由于文件描述符可能会被重用,因此可能会出现一些难以理解的竞争条件,导致意外的副作用。” 你可以使用select而不是accept,并等待来自另一个线程的某些事件,然后在监听线程中关闭套接字。

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