Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在单线程Python应用程序中关闭socketserver永远服务_Python_Sockets_Socketserver - Fatal编程技术网

在单线程Python应用程序中关闭socketserver永远服务

在单线程Python应用程序中关闭socketserver永远服务,python,sockets,socketserver,Python,Sockets,Socketserver,我知道socketserver有一个方法shutdown(),它会导致服务器关闭,但这只适用于多线程应用程序,因为关闭需要从不同的线程调用,而不是运行serve\u forever()的线程 我的应用程序一次只处理一个请求,因此我不使用单独的线程来处理请求,而且我无法调用shutdown(),因为它会导致死锁(它不在文档中,但直接在socketserver的源代码中声明) 为了更好地理解,我将在此处粘贴代码的简化版本: import socketserver class TCPServerV4

我知道socketserver有一个方法
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,服务器将从同一线程正确关闭。我得到一个