web-dev-qa-db-ja.com

1スレッドでソケットサーバーserve_forever()をシャットダウンするPythonアプリケーション

Socketserverにはサーバーをシャットダウンするメソッドshutdown()があることは知っていますが、シャットダウンはserve_forever()が実行されているスレッドとは別のスレッドから呼び出す必要があるため、複数のスレッドアプリケーションでのみ機能します。

私のアプリケーションは一度に1つの要求のみを処理するため、要求の処理に個別のスレッドを使用しません。デッドロックを引き起こすため、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()

このコードが機能していないことを知っています。ユーザーがCtrl-Cを押したときに受信データを待っている間にサーバーをシャットダウンしてアプリケーションを終了するという、達成したいことをお見せしたかっただけです。

18
samuelg0rd0n

ハンドラでローカルに別のスレッドを開始し、そこから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()
_

いくつかのメモ:

  1. サーバーを強制終了するには、[〜#〜] post [〜#〜]リクエストを_http://localhost:8000/kill_server_に送信します。
  2. server.shutdown()を呼び出す関数を作成し、それを別のスレッドから実行して、説明する問題を解決します。
  3. 私は https://stackoverflow.com/a/18858817/774971 からのアドバイスを使用して、ソケットをすぐに再利用できるようにします(なしでサーバーを再度実行できます(Errno 98)アドレスは既に使用されていますエラー)。標準のTCPServerを使用すると、サーバーが再度実行されるまで、接続がタイムアウトになるまで待機する必要があります。
14
dmitry_romanov

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
_

サーバーはシャットダウンします。これはserver.shutdown()を呼び出すのと同じことを行いますが、シャットダウンするまで待機する(そしてメインスレッドをデッドロックする)ことはありません。

6
frmdstryr

OPの目的は、リクエストハンドラーからサーバーをシャットダウンすることであり、彼のコードのKeyboardInterruptの側面は混乱を招くものだと思う

サーバーが実行されているシェルから_ctrl-c_を押すと成功します特別なことをせずにサーバーをシャットダウンします。別のシェルから_ctrl-c_を押して機能することを期待することはできません。 may という概念が、この混乱するコードの出所だと思います。 OPが試みたようにhandle()KeyboardInterruptを処理する必要はなく、別の提案のようにserve_forever()で処理する必要もありません。どちらも実行しない場合、期待どおりに動作します。

ここでの唯一のトリックは-そしてトリッキーです-デッドロックせずにハンドラーからシャットダウンするようサーバーに指示することです。

OPがコードで説明して示したように、彼はシングルスレッドサーバーを使用しているため、「他のスレッド」でシャットダウンすることを提案したユーザーは注意を払っていません。

SocketServerコードを調べたところ、BaseServerクラスは、このモジュールで使用可能なスレッド化されたミックスインを処理するために、実際に_threading.Event_ _serve_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
_
3
chadrik

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()
2
Ondergetekende

実際にはソースコードで指摘されているように、他のスレッドで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()
2
ykhrustalev

あなたはいつでも信号を試すことができます:

インポート信号インポートOS

#シャットダウンを決定したハンドラーコード内:

os.kill(os.getpid()、signal.SIGHUP)#自分にメッセージを送る

1
upandacross