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を押したときに受信データを待っている間にサーバーをシャットダウンしてアプリケーションを終了するという、達成したいことをお見せしたかっただけです。
ハンドラでローカルに別のスレッドを開始し、そこから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()
_
いくつかのメモ:
http://localhost:8000/kill_server
_に送信します。server.shutdown()
を呼び出す関数を作成し、それを別のスレッドから実行して、説明する問題を解決します。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()
を呼び出すのと同じことを行いますが、シャットダウンするまで待機する(そしてメインスレッドをデッドロックする)ことはありません。
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
_
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()
実際にはソースコードで指摘されているように、他のスレッドで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()
あなたはいつでも信号を試すことができます:
インポート信号インポートOS
#シャットダウンを決定したハンドラーコード内:
os.kill(os.getpid()、signal.SIGHUP)#自分にメッセージを送る