在单线程Python应用程序中关闭socketserver serve_forever()

sam*_*d0n 18 python sockets socketserver

我知道socketserver有一个方法shutdown()导致服务器关闭但这只适用于多线程应用程序,因为需要从运行serve_forever()的线程以外的线程调用shutdown.

我的应用程序一次只处理一个请求所以我不使用单独的线程来处理请求,我无法调用shutdown(),因为它会导致死锁(它不在文档中,但它直接在socketserver的源代码中声明).

我会在这里粘贴我的代码的简化版本以便更好地理解:

import socketserver

class TCPServerV4(socketserver.TCPServer):
  address_family = socket.AF_INET
  allow_reuse_address = True

class TCPHandler(socketserver.BaseRequestHandler):
  def handle(self):
    try:
       data = self.request.recv(4096)
    except KeyboardInterrupt:
       server.shutdown()

server = TCPServerV4((host, port), TCPHandler)
server.server_forever()
Run Code Online (Sandbox Code Playgroud)

我知道这段代码不起作用.我只想向您展示我想要完成的事情 - 当用户按下Ctrl-C时关闭服务器并在等待传入数据时退出应用程序.

dmi*_*nov 16

你可以在你的处理程序中本地启动另一个线程,然后shutdown从那里调用.

工作演示:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-

    import SimpleHTTPServer
    import SocketServer
    import time
    import thread

    class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
        def do_POST(self):
            if self.path.startswith('/kill_server'):
                print "Server is going down, run it again manually!"
                def kill_me_please(server):
                    server.shutdown()
                thread.start_new_thread(kill_me_please, (httpd,))
                self.send_error(500)

    class MyTCPServer(SocketServer.TCPServer):
        def server_bind(self):
            import socket
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.socket.bind(self.server_address)

    server_address = ('', 8000)
    httpd = MyTCPServer(server_address, MyHandler)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
Run Code Online (Sandbox Code Playgroud)

几点说明:

  1. 要杀死服务器,请执行POST请求http://localhost:8000/kill_server.
  2. 我创建函数,server.shutdown()从另一个线程调用并运行它来解决我们讨论的问题.
  3. 我使用来自/sf/answers/1320117221/的建议使套接字立即可用于重用(您可以再次运行服务器而无需使用[Errno 98]地址错误).使用TCPServer库存,您将不得不等待连接超时再次运行服务器.


frm*_*ryr 7

SocketServer库使用一些奇怪的方式来处理继承的属性(由于使用了旧样式类,因此进行了猜测)。如果创建服务器并列出其受保护的属性,则会看到:

In [4]: server = SocketServer.TCPServer(('127.0.0.1',8000),Handler)
In [5]: server._

server._BaseServer__is_shut_down
server.__init__
server._BaseServer__shutdown_request
server.__module__
server.__doc__
server._handle_request_nonblock
Run Code Online (Sandbox Code Playgroud)

如果只是在请求处理程序中添加以下内容:

self.server._BaseServer__shutdown_request = True
Run Code Online (Sandbox Code Playgroud)

服务器将关闭。这样做与调用相同server.shutdown(),只是不等待(并死锁主线程)直到关机。

  • 丑陋但仍是我发现的最干净的方法:/标准库应具有nowait参数,以便可以在同一线程中调用 (2认同)

ykh*_*lev 5

shutdown实际上应该按照源代码中的指示在其他线程中调用:

 def shutdown(self):
        """Stops the serve_forever loop.

        Blocks until the loop has finished. This must be called while
        serve_forever() is running in another thread, or it will
        deadlock.
        """
        self.__shutdown_request = True
        self.__is_shut_down.wait()
Run Code Online (Sandbox Code Playgroud)


Ond*_*nde 2

如果您没有在 TCP 处理程序中捕获KeyboardInterrupt(或者重新引发它),它应该向下渗透到根调用,在本例中为调用server_forever()

不过,我还没有测试过这一点。代码如下所示:

import socketserver  # Python2: SocketServer

class TCPServerV4(socketserver.TCPServer):
    address_family = socket.AF_INET
    allow_reuse_address = True

class TCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(4096)

server = TCPServerV4((host, port), TCPHandler)

try:
    server.serve_forever()
except KeyboardInterrupt:
    server.shutdown()
Run Code Online (Sandbox Code Playgroud)