基于 socketserver 的 Python 3 服务器关闭挂起

mar*_*ark 6 python gevent socketserver python-3.x

我正在使用 Python 3 中的线程 SocketServer 开发一个“简单”服务器。

为此,我在实施关闭方面遇到了很多麻烦。我在互联网上找到的下面的代码最初可以工作,但在通过 telnet 从客户端发送一些命令后停止工作。一些调查告诉我它挂在 threading._shutdown... threading._wait_for_tstate_lock 中,但到目前为止这还没有敲响警钟。

我的研究告诉我,关于如何在不同的 Python 版本中做到这一点,有大约 42 种不同的解决方案、框架等。到目前为止我找不到 python3 的工作方法。例如,我喜欢python 2.7 的telnetsrvhttps://pypi.python.org/pypi/telnetsrv/0.4)(它使用 gevent 中的 greenlets),但这个不适用于 python 3。所以如果有更多 pythonic,std lib 方法或可靠工作的方法我很想听听!

我目前的赌注是使用套接字服务器,但我还不知道如何处理挂起的服务器。我删除了所有日志语句和大部分功能,因此我可以发布这个暴露问题的最小服务器:

# -*- coding: utf-8 -*-
import socketserver
import threading

SERVER = None


def shutdown_cmd(request):
    global SERVER
    request.send(bytes('server shutdown requested\n', 'utf-8'))
    request.close()
    SERVER.shutdown()
    print('after shutdown!!')
    #SERVER.server_close()


class service(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                msg = str(self.request.recv(1024).strip(), 'utf-8')
                if msg == 'shutdown':
                    shutdown_cmd(msg, self.request)
                else:
                    self.request.send(bytes("You said '{}'\n".format(msg), "utf-8"))
            except Exception as e:
                pass


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def run():
    global SERVER
    SERVER = ThreadedTCPServer(('', 1520), service)
    server_thread = threading.Thread(target=SERVER.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    input("Press enter to shutdown")
    SERVER.shutdown()


if __name__ == '__main__':
    run()
Run Code Online (Sandbox Code Playgroud)

如果能够从处理程序中停止服务器那就太好了(请参阅 shutdown_cmd)

geo*_*xsh 7

shutdown()按预期工作,服务器已停止接受新连接,但 python 仍在等待活动线程终止。

默认情况下,socketserver.ThreadingMixIn将创建新线程来处理传入连接,并且默认情况下,这些线程是非守护线程,因此 python 将等待所有活动的非守护线程终止。

当然,你可以让服务器生成守护线程,那么 python 将不会等待:

ThreadingMixIn 类定义了一个属性 daemon_threads,它指示服务器是否应该等待线程终止。如果您希望线程自主运行,则应该显式设置该标志;默认值为 False,这意味着在 ThreadingMixIn 创建的所有线程退出之前,Python 不会退出。

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    daemon_threads = True
Run Code Online (Sandbox Code Playgroud)

但这不是理想的解决方案,您应该检查为什么线程永远不会终止,通常,当没有新数据可用或客户端关闭连接时,服务器应该停止处理连接:

import socketserver
import threading


shutdown_evt = threading.Event()


class service(socketserver.BaseRequestHandler):
    def handle(self):
        self.request.setblocking(False)
        while True:
            try:
                msg = self.request.recv(1024)
                if msg == b'shutdown':
                    shutdown_evt.set()
                    break
                elif msg:
                    self.request.send(b'you said: ' + msg)
                if shutdown_evt.wait(0.1):
                    break
            except Exception as e:
                break


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def run():
    SERVER = ThreadedTCPServer(('127.0.0.1', 10000), service)
    server_thread = threading.Thread(target=SERVER.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    input("Press enter to shutdown")
    shutdown_evt.set()
    SERVER.shutdown()


if __name__ == '__main__':
    run()
Run Code Online (Sandbox Code Playgroud)


mar*_*ark 2

我尝试了两种解决方案来实现在 Linux 和 Windows 上的 Python 3 上运行的 tcp 服务器(我尝试了 Windows 7):

  • 使用套接字服务器(我的问题)-关闭不起作用
  • 使用 asyncio (发布了答案) - 在 Windows 上不起作用

这两种解决方案都是基于网络上的搜索结果。最后我不得不放弃寻找经过验证的解决方案的想法,因为我找不到。因此我实现了自己的解决方案(基于 gevent)。我将其发布在这里是因为我希望这对其他人有所帮助,以避免像我一样陷入困境。

# -*- coding: utf-8 -*-
from gevent.server import StreamServer
from gevent.pool import Pool


class EchoServer(StreamServer):

    def __init__(self, listener, handle=None, spawn='default'):
        StreamServer.__init__(self, listener, handle=handle, spawn=spawn)

    def handle(self, socket, address):
        print('New connection from %s:%s' % address[:2])
        socket.sendall(b'Welcome to the echo server! Type quit to exit.\r\n')

        # using a makefile because we want to use readline()
        rfileobj = socket.makefile(mode='rb')
        while True:
            line = rfileobj.readline()
            if not line:
                print("client disconnected")
                break
            if line.strip().lower() == b'quit':
                print("client quit")
                break
            if line.strip().lower() == b'shutdown':
                print("client initiated server shutdown")
                self.stop()
                break
            socket.sendall(line)
            print("echoed %r" % line.decode().strip())
        rfileobj.close()


srv = EchoServer(('', 1520), spawn=Pool(20))
srv.serve_forever()
Run Code Online (Sandbox Code Playgroud)