无法关闭侦听套接字以中止来自单独线程的accept()/ select()

我正在编写要使用阻塞I / O的Python(v3.7.3)套接字服务器。我使用select()并没有超时来接受新客户以及从中读取信息。我可以关闭监听套接字以中止select(),并捕获OSError作为停止执行的指示。

但是,当在单独的线程中运行时,这似乎不起作用,我也不明白为什么。

我了解还有其他方法可以完成此操作,例如使用超时,对select()使用虚拟套接字,或与侦听器建立虚拟连接以将其唤醒。但是所有这些在某种程度上都违反了使用select()的目的,并且在单线程中运行时并不必要。

这是重现此问题的基本示例,在我的实际代码中,它仅代表多个线程(因此,我首先使用线程):

#!/usr/bin/env python3

import signal
import socket
import threading


class SocketCloseTest:
    """Simple test case for using socket.close() to abort select.select()"""

    def __init__(self,port,address=None):
        self.port = port
        self.address = address or ''

        self.socket = None

    def stop(self):
        """Close listening socket to stop select.select()"""

        if self.socket:
            print("Closing listener",self.socket)
            self.socket.close()
            print("Listener closed",self.socket)

    def threaded_run(self):
        """Run test in a separate thread"""

        thread = threading.Thread(target=self.run)
        print("Starting sub-thread")
        thread.start()
        thread.join()
        print("Sub-thread ended")

    def run(self):
        """Run test"""

        self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # Reuse port for quick re-launch of the application
        self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        self.socket.bind((self.address,self.port))
        print("Starting listener")
        self.socket.listen()

        try:
            print("select() started")
            r,w,e = select.select([self.socket],[],[])
        except OSError:
            print("select() aborted")
        else:
            print("select() completed")


if __name__ == '__main__':
    tester = SocketCloseTest(5000,address='')

    # Set up signal handler for Ctrl-C
    def signal_handler(signum,frame):
        print("Received signal {}".format(signum))
        tester.stop()
    signal.signal(signal.SIGINT,signal_handler)

    # This works
    tester.run()

    # This doesn't work
    # tester.threaded_run()

    print("Main thread ended")

使用test.run()时,它会按预期运行并导致以下结果:

  

启动监听器
  选择开始
  ^ C接收到的信号2
  关闭监听器
  侦听器关闭
  选择已取消
  主线程结束

但是,当与tester.threaded_run()一起运行时,它只是挂在对select()的调用应中止的位置。奇怪的是,此时将作业放在后台会导致代码继续执行,应按以下步骤进行:

  

启动子线程
  启动监听器
  选择开始
  ^ C接收到的信号2
  关闭监听器
  侦听器关闭
  -按下Ctrl-Z可以在Shell中挂起作业-

     

$ bg
  -壳牌在后台报告工作-
  选择已取消
  子线程已结束
  主线程结束

谢谢...

  • 编辑提到accept()患有完全相同的症状。
weiqingtao 回答:无法关闭侦听套接字以中止来自单独线程的accept()/ select()

close()在多线程情况下不会执行您想要的操作。请改用您描述的其他机制之一。

在单线程情况下,控制权返回到select(),该命令将重新启动并注意到现在已废弃的文件描述符上的EBADF。 (当然,这是非常危险的,因为尽管玩具程序看起来很安全,但fd#3可能随时被任何其他线程甚至是复杂的信号处理程序回收,)。在多线程情况下,{{1 }}只是不会唤醒您的close() ing线程。

Python docs warn

  

注意: select()释放与连接关联的资源,但不一定立即关闭连接。如果您想及时关闭连接,请在close()之前致电shutdown()

实际上,这是一个相对棘手且依赖平台的问题。摘录2008 article in the venerable Dr Dobb's

  

在某些操作系统上,[ shutdown()而不是 close()]也是唯一可行的解​​决方案:在FreeBSD上, close()没有 shutdown()的设备不会唤醒在 read() select() ....中等待的进程。 >      

要考虑的另一个问题是,通过 shutdown()或通过 close()关闭可能不被视为操作系统中的读取事件。...

     

但是, shutdown()仅适用于已建立连接的套接字,不适用于侦听新连接的套接字或其他类型的文件描述符。...

(出于价值,在我的系统上,close() 唤醒了shutdown() ing线程。)

本文链接:https://www.f2er.com/3114167.html

大家都在问