我知道socketserver有一个方法shutdown()导致服务器关闭但这只适用于多线程应用程序,因为需要从运行serve_forever()的线程以外的线程调用shutdown。
我的应用程序一次只处理一个请求所以我不会使用单独的线程来处理请求,我无法调用shutdown(),因为它会导致死锁(它不在文档中,但它&# 39; s直接在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时等待传入数据时关闭服务器并退出应用程序。
答案 0 :(得分:13)
您可以在处理程序中本地启动另一个线程,然后从那里调用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()
的函数并从另一个线程运行它来解决我们讨论的问题。答案 1 :(得分:4)
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()
完全相同,只是没有等待(并且主线程死锁),直到它关闭。
答案 2 :(得分:3)
我认为OP的意图是从请求处理程序关闭服务器,而我认为他的代码的KeyboardInterrupt
方面只是令人困惑。
在服务器运行的shell中按ctrl-c
将成功关闭它而不做任何特殊操作。你不能从另一个shell中按ctrl-c
并希望它能够正常工作,我认为概念可能是这个令人困惑的代码来自的地方。没有必要像OP尝试的那样处理KeyboardInterrupt
中的handle()
,或者根据另一个建议来处理serve_forever()
。如果你不这样做,它会按预期工作。
这里唯一的技巧 - 它很棘手 - 告诉服务器从处理程序关闭而没有死锁。
正如OP在他的代码中解释和显示的那样,他正在使用单线程服务器,所以建议在“其他线程”中关闭它的用户没有注意。
我挖掘了SocketServer
代码并发现BaseServer
类在使用此模块中可用的线程mixin时,实际上使得使用非线程服务器变得更加困难,在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 :(得分:2)
如果你没有在tcp处理程序中捕获KeyboardInterrupt
(或者你重新提升它),它应该逐渐转向根调用,在这种情况下是server_forever()
调用。 / p>
但是,我没有测试过这个。代码如下所示:
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()
答案 4 :(得分:2)
你应该在源代码中指出的其他线程中调用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()
答案 5 :(得分:1)
你可以随时尝试信号:
导入信号 import os
#inside决定关闭的处理程序代码:
os.kill(os.getpid(),signal.SIGHUP)#small yourself sighup
答案 6 :(得分:0)
我在Python 3.7.4中使用了此
def handle():
setattr(self.server, '_BaseServer__shutdown_request', True)