2012-04-10 15 views
11

Ich weiß, dass Socket ein Verfahren Abschaltung hat(), die Server funktioniert aber nur in mehrere Threads Anwendung herunterzufahren, verursacht, da die Abschaltung von anderen Thread aufgerufen werden muss als die Thread wo serve_forever() ausgeführt wird.Shutdown Socket serve_forever() in einer Eins-Thread Python Anwendung

Meine Anwendung behandelt nur eine Anfrage zu der Zeit, so dass ich nicht separate Threads für die Behandlung von Anfragen verwenden und ich kann nicht aufrufen, da es Deadlock verursacht (es ist nicht in der Dokumentation, aber es wird direkt im Quellcode angegeben von Socketserver).

Ich werde hier vereinfachte Version meines Codes für ein besseres Verständnis einfügen:

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() 

ich weiß, dass dieser Code nicht funktioniert. Ich wollte Ihnen nur zeigen, was ich erreichen möchte - den Server herunterfahren und die Anwendung beenden, während ich auf die eingehenden Daten warte, wenn der Benutzer Strg-C drückt.

+0

separaten Thread: http://StackOverflow.com/Questions/268629/How-to-Stop-Basehttpserver-serve-Foree-in-A-Basehttprequesthandler-Unterklasse –

Antwort

2

Wenn Sie nicht die KeyboardInterrupt im tcp-Handler abfangen (oder wenn Sie es erneut anheben), sollte es bis zum root-Aufruf hinunterrieseln, in diesem Fall der server_forever() Aufruf.

ich nicht getestet haben, though. Der Code würde wie folgt aussehen:

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() 
+1

Leider funktioniert es nicht. Ich habe schon so etwas probiert. Es schreibt eine Warnung "Ausnahme aufgetreten" im Terminal und schließt die Verbindung (Handle-Methode gibt zurück), aber serve_forver wartet immer noch auf eingehende Verbindungen und sobald eine akzeptiert wird, wird die Anwendung normal fortgesetzt. Um die Anwendung vollständig zu beenden, muss ich Strg-C drücken, damit die Warnung "Ausnahme aufgetreten" erscheint, und dann Strg-C erneut, bevor eine Verbindung hergestellt wird. – samuelg0rd0n

+0

Ich persönlich denke, dass es nicht möglich ist, und ich habe nur zwei Optionen: entweder Handle-Methode in anderen Thread oder nicht Modul socketserver zu verwenden und daher Server manuell erstellen (Socket, binden, hören etc.) – samuelg0rd0n

2

Sie shutdown tatsächlich in anderen Thread aufrufen sollte wie im Quellcode darauf:

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() 
2

Ich glaube, die Absicht des OP ist der Server aus dem Request-Handler schließen und ich denke, , dass der KeyboardInterrupt Aspekt seines Codes nur Dinge verwirrend ist.

Drücken von ctrl-c aus der Shell, in der der Server läuft wird erfolgreich in herunterfahren, ohne etwas Besonderes zu tun. Sie können nicht ctrl-c aus einer anderen Schale drücken und erwarten, dass es zu arbeiten, und ich denke, dass Begriff kann sein, wo dies verwirrend Code herkommt. Es gibt keine Notwendigkeit, die KeyboardInterrupt in handle() zu handhaben als die OP versucht, oder um serve_forever() als andere vorgeschlagen. Wenn Sie dies nicht tun, funktioniert es wie erwartet.

Der einzige Trick hier - und es ist schwierig - ist der Server zum Herunterfahren aus dem Handler zu sagen ohne Deadlocks.

Als OP erklärt und in seinem Code gezeigt, er ist ein Single-Threaded-Server, so dass der Benutzer, der es in der „anderen Thread“ shut vorgeschlagen, nach unten ist nicht aufpasst.

grub ich um den SocketServer Code und entdecken, dass die BaseServer Klasse, in seinem Bemühen, mit dem Gewinden Mixins in diesem Modul zu arbeiten, macht es tatsächlich schwieriger mit gewindelosen Servern zu verwenden, indem ein threading.Event um die Verwendung von Schleife in serve_forever.

Also schrieb ich eine modifizierte Version von serve_forever für single-threaded-Server, die es ermöglicht, den Server vom Request-Handler herunterzufahren.

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() 

Wenn Sie die Zeichenfolge 'shutdown' an den Server senden, wird der Server seine serve_forever Schleife beenden. Sie können netcat zu testen, verwenden:

printf "shutdown" | nc localhost 52000 
+0

Ich bekomme a => r, w , e = SocketServer._eintr_retry (select.select, [selbst], [], [], AttributError: 'Modul' Objekt hat kein Attribut '_eintr_retry' – KumZ

8

Sie anderen Thread lokal starten können, in Ihrem Handler und shutdown von dort anrufen.

Arbeits Demo:

#!/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() 

Ein paar Hinweise:

  1. Server zu töten, tun POST Anfrage an http://localhost:8000/kill_server.
  2. Ich erstelle Funktion, die server.shutdown() aufruft und es von einem anderen Thread laufen lassen, um das Problem zu lösen, das wir besprechen.
  3. Ich benutze Rat von https://stackoverflow.com/a/18858817/774971, um Socket sofort für die Wiederverwendung verfügbar machen (Sie können den Server wieder ohne [Errno 98] Adresse bereits in Verwendung Fehler laufen). Mit dem TCPServer-Lager müssen Sie auf die Verbindung warten, bis die Zeit abgelaufen ist, um den Server wieder zu starten.
+0

Warum die '' time.sleep (0.3) ''? –

+0

Um dem Thread etwas Zeit zu geben, den 'server.shutdown' -Stoß zu beenden. Laut Mann wird der Thread beim Funktionsexit getötet, und ich dachte, es wäre eine gute Idee, ihn für eine Weile am Leben zu erhalten. Ich erinnere mich nicht mehr genau und ich habe den Code nicht umgestaltet, nachdem ich diesen Proof of Concept geschrieben hatte. –

+0

Das ist nicht notwendig: Der '' shutdown() '' Aufruf [blockiert bis '' serve_forever() '' wurde gestoppt] (https://docs.python.org/3.3/library/socketserver.html#socketserver.BaseServer. Herunterfahren). Dies gilt für alle Python-Versionen seit 2.6, als 'shutdown()' 'eingeführt wurde. –

1

Sie könnten immer Signale versuchen:

Importsignal import os

# innerhalb Handler-Code, der zum Herunterfahren entschieden hat:

os.kill (os.getpid(), Signal .SIGHUP) # send mich sighup

2

Die SocketServer-Bibliothek verwendet einige seltsame Methoden zur Verarbeitung von geerbten Attributen (Raten aufgrund der Verwendung alter Stilklassen). Wenn Sie einen Server erstellen und geben Sie es geschützt ist Attribute, die Sie sehen:

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 

Wenn Sie die nur hinzufügen, folgendes in Request-Handler:

self.server._BaseServer__shutdown_request = True 

Der Server wird heruntergefahren. Dies geschieht genauso wie das Aufrufen von server.shutdown() ohne Warten (und Deadlocking des Hauptthreads) bis zum Herunterfahren.

+1

Hässlich, aber immer noch der sauberste Weg, den ich gefunden habe:/Die Standard-Bibliothek sollte ein Nowait-Argument haben, so dass es im selben Thread aufgerufen werden kann –