Python socket accept 阻塞 - 导致应用无法退出

9 投票
2 回答
6826 浏览
提问于 2025-04-15 12:59

我写了一个非常简单的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 个回答

4

我不推荐在正常关闭时使用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()返回的是文件描述符,而不是我们可以写入的文件对象。)我没有深入研究,因为这和问题不是特别相关。

9

__init__方法中,self.start()之前加上self.setDaemon(True)

(在Python 2.6及以后的版本中,推荐使用self.daemon = True)。

这里的关键点可以在这个链接中找到:

当没有活着的非守护线程时,整个Python程序会退出。

所以,你需要把那些不应该仅仅因为自己活着就让整个程序继续运行的线程设置为“守护线程”。顺便说一下,主线程总是非守护线程。

撰写回答