在单线程Python应用程序中关闭socketserver永远服务
我知道socketserver有一个方法在单线程Python应用程序中关闭socketserver永远服务,python,sockets,socketserver,Python,Sockets,Socketserver,我知道socketserver有一个方法shutdown(),它会导致服务器关闭,但这只适用于多线程应用程序,因为关闭需要从不同的线程调用,而不是运行serve\u forever()的线程 我的应用程序一次只处理一个请求,因此我不使用单独的线程来处理请求,而且我无法调用shutdown(),因为它会导致死锁(它不在文档中,但直接在socketserver的源代码中声明) 为了更好地理解,我将在此处粘贴代码的简化版本: import socketserver class TCPServerV4
shutdown()
,它会导致服务器关闭,但这只适用于多线程应用程序,因为关闭需要从不同的线程调用,而不是运行serve\u 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()
我知道该代码不起作用。我只是想向您展示我想要完成的事情—当用户按下Ctrc时,关闭服务器并退出应用程序,同时等待传入数据。如果您没有捕获TCP处理程序中的
键盘中断
(或者,如果您重新启动它),它应该会向下进入根调用,在本例中,server\u 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()
我相信OP的意图是从请求处理程序关闭服务器,我认为他的代码的
键盘中断
方面只是让人困惑
在服务器运行的shell中按CtrlC将成功地关闭它,而无需执行任何特殊操作。您不能从另一个shell中按下CtrlC并期望它工作,我认为这个概念可能就是这个令人困惑的代码的来源。无需像OP尝试的那样在handle()
中处理键盘中断,也无需像另一个建议的那样在永远服务()
附近处理。如果两者都不做,它将按预期工作
这里唯一的诀窍——也是很棘手的——是告诉服务器从处理程序关闭而不死锁
正如OP在他的代码中解释和显示的那样,他使用的是单线程服务器,因此建议在“其他线程”中关闭它的用户没有注意到
我仔细研究了SocketServer
代码,发现BaseServer
类在使用本模块中可用的线程混合时,实际上通过在永远服务
中的循环中使用线程化.Event
使非线程服务器更难使用
因此,我为单线程服务器编写了一个修改版的serve\u 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'
发送到服务器,服务器将结束其永远服务
循环。您可以使用netcat来测试这一点:
printf "shutdown" | nc localhost 52000
您可以在处理程序中本地启动另一个线程,然后从那里调用shutdown
工作演示:
#/usr/bin/env python
#-*-编码:UTF-8-*-
导入SimpleHTTPServer
导入SocketServer
导入时间
导入线程
类MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_POST(自我):
如果self.path.startswith('/kill_server'):
打印“服务器正在关闭,请手动再次运行!”
def杀死我(服务器):
server.shutdown()
线程。启动新线程(请杀死我(httpd)
self.send_错误(500)
类MyTCPServer(SocketServer.TCPServer):
def服务器绑定(自):
导入套接字
self.socket.setsockopt(socket.SOL_socket,socket.SO_REUSEADDR,1)
self.socket.bind(self.server\u地址)
服务器地址=('',8000)
httpd=MyTCPServer(服务器地址,MyHandler)
尝试:
httpd.永远为你服务()
除键盘中断外:
通过
httpd.server_close()
几点注意:
要杀死服务器,请向http://localhost:8000/kill_server
我创建了调用server.shutdown()
的函数,并从另一个线程运行它来解决我们讨论的问题
我使用来自的建议使套接字立即可供重用(您可以再次运行服务器,而不必使用[Errno 98]地址错误)。使用stock TCPServer,您必须等待连接超时才能再次运行服务器
您可以尝试以下信号:
导入信号
导入操作系统
#已决定关闭的内部处理程序代码:
os.kill(os.getpid(),signal.SIGHUP)#让我自己SIGHUP
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()
的操作相同,只是不需要等待(并死锁主线程)直到它关闭 我在Python 3.7.4中使用了它
def handle():
setattr(self.server,“\u BaseServer\u关机\u请求”,True)
不幸的是,它不起作用。我已经试过这样的东西了。它在终端中写入“exception happend”警告并关闭连接(handle方法返回),但SERVICE\u forver仍在等待传入的连接,一旦接收到一个连接,应用程序就会正常继续。要完全退出应用程序,我需要按Ctrl-C,这样“异常发生”警告就会出现,然后在建立任何连接之前再次按Ctrl-C。我个人认为这是不可能的,我只有两个选择:要么将句柄方法放在不同的线程中,要么不使用模块socketserver,因此手动创建服务器(socket、bind、listen等)类似于http.server.HTTPServer
这种简单的方法可以工作。如果您按住Ctrl+C,服务器将从同一线程正确关闭。我得到一个