Python套接字的accept方法阻塞 - 防止应用程序退出

8
我写了一个非常简单的Python类,用于在套接字上等待连接。目的是将此类插入现有应用程序中,并异步向连接的客户端发送数据。
问题在于,在等待socket.accept()时,我无法通过按下ctrl-c来结束应用程序。我也无法检测到我的类何时超出范围并通知它结束。
理想情况下,下面的应用程序应该在time.sleep(4)到期后退出。如下所示,我尝试使用select,但这也会阻止应用程序响应ctrl-c。如果我可以检测到主方法中变量'a'已经超出作用域,我可以设置退出标志(并减少select的超时时间以使其响应)。
有什么想法吗?
谢谢
import sys
import socket
import threading
import time
import select

class Server( threading.Thread ):
    def __init__( self, i_port ):
        threading.Thread.__init__( self )
        self.quitting = False
        self.serversocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
        self.serversocket.bind( (socket.gethostname(), i_port ) )
        self.serversocket.listen(5)
        self.start()

    def run( self ):
        # Wait for connection
        while not self.quitting:
            rr,rw,err = select.select( [self.serversocket],[],[], 20 )
            if rr:
                (clientsocket, address) = self.serversocket.accept()
                clientsocket.close()

def main():
    a = Server( 6543 )
    time.sleep(4)

if __name__=='__main__':
  main()

2个回答

9
self.start()之前在__init__中添加self.setDaemon(True)。(在Python 2.6及更高版本中,更喜欢使用self.daemon = True)。重要的想法在这里解释:当没有非守护线程存活时,整个Python程序将退出。因此,您需要使那些不应仅通过自身存在就保持整个进程活动的线程成为“守护线程”。顺便说一下,主线程始终是非守护线程。

谢谢,它起作用了。 我尝试了 self.daemon = True 但那没有任何效果(我正在Win32上运行python 2.5)。 把它改为 self.setDaemon(True) 就解决了!谢谢! - Brian O'Kennedy

4

我不推荐在正常关机过程中使用setDaemon特性。这样做并不规范;它并没有提供一条干净的线程关闭路径,而是直接杀死线程,无法进行清理。 如果主线程意外退出,设置setDaemon可以避免程序被卡住,但对于正常关机路径来说,除了快速修补之外,它并不是一个好的选择。

import sys, os, socket, threading, time, select

class Server(threading.Thread):
    def __init__(self, i_port):
        threading.Thread.__init__(self)
        self.setDaemon(True)
        self.quitting = False
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.serversocket.bind((socket.gethostname(), i_port))
        self.serversocket.listen(5)
        self.start()

    def shutdown(self):
        if self.quitting:
            return

        self.quitting = True
        self.join()

    def run(self):
        # Wait for connection
        while not self.quitting:
            rr,rw,err = select.select([self.serversocket],[],[], 1)
            print rr
            if rr:
                (clientsocket, address) = self.serversocket.accept()
                clientsocket.close()

        print "shutting down"
        self.serversocket.close()

def main():
    a = Server(6543)
    try:
        time.sleep(4)
    finally:
        a.shutdown()

if __name__=='__main__':
  main()

请注意,在调用shutdown()后,这可能会延迟一秒钟,这是不好的行为。通常很容易解决:创建一个wakeup pipe(),您可以向其中写入,并将其包含在select中; 但尽管这非常基本,我无法找到在Python中执行此操作的任何方法。(os.pipe()返回文件描述符,而不是我们可以写入的文件对象。)由于这与问题无关,因此我没有深入研究。

你可以使用os.fdopen将文件描述符包装成Python文件对象。但是,当线程中没有特定的清理工作时,我不同意需要更多的操作,只有setDaemon足矣,这只是一个小而可有可无的复杂性。 - Alex Martelli
我尝试了一种非常类似的方法 - 我的ShutDown方法只是设置了一个IsQuitting标志并连接到相关的套接字,从而解除了accept()调用的阻塞,允许线程退出。但它确实依赖于用户调用ShutDown()。不过还是谢谢你的建议。 - Brian O'Kennedy
对于任何比简单的黑客更大的项目,您都需要能够关闭线程而不退出整个进程。即使是只想记录“成功关闭”的简单服务器也需要这样做,否则在记录关闭后它仍将接受连接。 - Glenn Maynard
select 可以接受任何文件描述符或具有 .fileno() 方法的对象来提取它。fdopen 是无用的。 - edef

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