Shutdown socketserver serve_forever() в однопоточном приложении Python
Я знаю, что у 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()
Мне известно, что этот код не работает. Я просто хотел показать вам то, что я хотел бы выполнить, - завершить работу сервера и выйти из приложения во время ожидания входящих данных, когда пользователь нажимает Ctrl-C.
Ответы
Ответ 1
Вы можете запустить другой поток локально, в вашем обработчике и вызвать 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()
Несколько заметок:
- Чтобы убить сервер, выполните POST запрос
http://localhost:8000/kill_server
.
- Я создаю функцию, которая вызывает
server.shutdown()
и запускает ее из другого потока для решения проблемы, которую мы обсуждаем.
- Я использую совет fooobar.com/questions/107918/..., чтобы сделать сокет мгновенно доступным для повторного использования (вы можете снова запустить сервер, не имея [Errno 98] Address, уже использующего ошибку). С запасом TCPServer вам придется ждать подключения к таймауту, чтобы снова запустить сервер.
Ответ 2
В библиотеке 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()
, просто не дожидаясь (и заблокировав основной поток) до его завершения.
Ответ 3
Я полагаю, что намерение OP состоит в том, чтобы отключить сервер от обработчика запросов, и я думаю, что аспект KeyboardInterrupt
его кода просто путают вещи.
Нажатие ctrl-c
из оболочки, где работает сервер, удастся закрыть ее, не делая ничего особенного. Вы не можете нажать ctrl-c
из другой оболочки и ожидать, что она будет работать, и я думаю, что это может быть причина, из-за которой возникает этот запутанный код. Нет необходимости обрабатывать KeyboardInterrupt
в handle()
при попытке OP, или около 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
Ответ 4
Если вы не поймаете 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()
Ответ 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()
Ответ 6
Вы всегда можете попробовать сигналы:
Импортный сигнал
import os
# код внутреннего обработчика, который решил отключить:
os.kill(os.getpid(), signal.SIGHUP) # отправить себя sighup
Ответ 7
Я использовал это в Python 3.7.4
def handle():
setattr(self.server, '_BaseServer__shutdown_request', True)