21

我知道 socketserver 有一个shutdown()导致服务器关闭的方法,但这仅适用于多线程应用程序,因为需要从与serve_forever()正在运行的线程不同的线程调用关闭。

我的应用程序一次只处理一个请求,所以我不使用单独的线程来处理请求并且我无法调用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()

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

4

7 回答 7

20
于 2014-03-20T13:05:52.353 回答
14

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

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

self.server._BaseServer__shutdown_request = True

服务器将关闭。这与 call 的作用相同server.shutdown(),只是在关闭之前无需等待(并死锁主线程)。

于 2016-03-15T17:07:42.753 回答
4

我相信 OP 的意图是从请求处理程序中关闭服务器,我认为他的KeyboardInterrupt代码方面只是令人困惑。

CtrlC从运行服务器的 shell按下将成功关闭它,而无需执行任何特殊操作。您不能CtrlC从不同的外壳按下并期望它工作,我认为这个概念可能是这个令人困惑的代码的来源。没有必要像 OP 尝试的那样处理KeyboardInterruptin ,或者像另一个建议的那样处理。如果你都不做,它会按预期工作。handle()serve_forever()

这里唯一的技巧——而且很棘手——是告诉服务器从处理程序中关闭而不会死锁。

正如 OP 在他的代码中解释和显示的那样,他使用的是单线程服务器,因此建议在“其他线程”中关闭它的用户没有注意。

我仔细研究了SocketServer代码,发现BaseServer该类在努力使用该模块中可用的线程混合时,实际上通过threading.Eventserve_forever.

serve_forever因此,我为单线程服务器编写了一个修改版本,它可以从请求处理程序中关闭服务器。

import SocketServer
import socket
import select

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

    def __init__(self, server_address, RequestHandlerClass):
        SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
        self._shutdown_request = False

    def serve_forever(self, poll_interval=0.5):
        """provide an override that can be shutdown from a request handler.
        The threading code in the BaseSocketServer class prevented this from working
        even for a non-threaded blocking server.
        """
        try:
            while not self._shutdown_request:
                # XXX: Consider using another file descriptor or
                # connecting to the socket to wake this up instead of
                # polling. Polling reduces our responsiveness to a
                # shutdown request and wastes cpu at all other times.
                r, w, e = SocketServer._eintr_retry(select.select, [self], [], [],
                                       poll_interval)
                if self in r:
                    self._handle_request_noblock()
        finally:
            self._shutdown_request = False

class TCPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(4096)
        if data == "shutdown":
            self.server._shutdown_request = True

host = 'localhost'
port = 52000
server = TCPServerV4((host, port), TCPHandler)
server.serve_forever()

如果您将字符串发送'shutdown'到服务器,服务器将结束其serve_forever循环。您可以使用 netcat 进行测试:

printf "shutdown" | nc localhost 52000
于 2014-01-29T20:57:52.260 回答
3

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()
于 2013-08-18T08:01:46.647 回答
2

如果你没有KeyboardInterrupt在 TCP 处理程序中捕获 (或者如果你重新引发它),它应该向下渗透到根调用,在这种情况下是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()
于 2012-04-10T09:27:49.163 回答
1

你总是可以尝试信号:

import signal
import os

# inside handler code that has decided to shutdown:
os.kill(os.getpid(), signal.SIGHUP)    # send myself sighup
于 2014-09-10T17:14:19.180 回答
1

我在 Python 3.7.4 中使用了它

def handle():
    setattr(self.server, '_BaseServer__shutdown_request', True)
于 2019-09-25T05:31:10.750 回答