2008-10-07 4 views
43

Ich habe einen einfachen multi-threaded Spiel-Server in Python geschrieben, der einen neuen Thread für jede Client-Verbindung erstellt. Ich finde, dass hin und wieder der Server wegen eines Fehlers broken-pipe/SIGPIPE abstürzt. Ich bin ziemlich sicher, dass es passiert, wenn das Programm versucht, eine Antwort zurück zu einem Client zu senden, der nicht mehr vorhanden ist.Wie behandelt man eine defekte Pipe (SIGPIPE) in Python?

Was ist ein guter Weg, damit umzugehen? Meine bevorzugte Lösung würde einfach die serverseitige Verbindung zum Client schließen und weitergehen, anstatt das gesamte Programm zu beenden.

PS: This Frage/Antwort befasst sich mit dem Problem in einer generischen Art und Weise; Wie genau soll ich das lösen?

Antwort

36

Lesen Sie auf dem Versuch: Aussage.

try: 
    # do something 
except socket.error, e: 
    # A socket error 
except IOError, e: 
    if e.errno == errno.EPIPE: 
     # EPIPE error 
    else: 
     # Other error 
+0

Wenn ich einen Versuch mache: #etwas außer: #anything, wird es nur irgendetwas fangen, und nicht nur IOErrors? –

+5

Die Decke ist eine schlechte Politik. Aber es wird jede Art von Ausnahme fangen. Du weißt, es ist ein IOError. Regle dies. Wenn etwas anderes auftaucht, ergründen Sie warum und behandeln Sie es entsprechend. Sie wollen keine Fehler wie Division durch Null oder nicht genügend Speicher maskieren. –

+1

Wenn Sie Pythons Socket-Modul verwenden, erhalten Sie keine IOError-Ausnahme: Sie erhalten eine socket.error Ausnahme. – mhawke

3

SIGPIPE (obwohl ich vielleicht denken, meinen Sie EPIPE?) Tritt auf Buchsen, wenn Sie einen Sockel herunterfahren und dann Daten versenden. Die einfache Lösung besteht darin, den Socket nicht herunterzufahren, bevor versucht wird, Daten zu senden. Dies kann auch auf Pipes passieren, aber es klingt nicht so, als ob es sich um einen Netzwerkserver handelt.

Sie können auch das Pflaster anwenden, um die Ausnahme in einem Top-Level-Handler in jedem Thread abzufangen.

Natürlich, wenn Sie Twisted anstatt einen neuen Thread für jede Client-Verbindung zu erstellen, würden Sie wahrscheinlich nicht dieses Problem haben. Es ist wirklich schwierig (vielleicht unmöglich, je nach Anwendung), die Reihenfolge von Close- und Write-Operationen korrekt zu bestimmen, wenn mehrere Threads mit demselben I/O-Kanal arbeiten.

+0

* Die einfache Lösung besteht darin, den Socket nicht herunterzufahren, bevor versucht wird, Daten zu senden. * Sie gehen hier davon aus, dass der Socket lokal (auf der Serverseite) heruntergefahren wurde, während in [this] (http://stackoverflow.com/) a/11866962/95735) antworten wir lesen, dass * Dies geschieht in der Regel, wenn Sie auf der anderen (Client-) Seite auf einen vollständig geschlossenen Socket schreiben. * Haben Sie diesen Fall absichtlich weggelassen oder stimmen Sie dieser Aussage nicht zu? –

-3

Meine Antwort ist ganz in der Nähe S.Lott ist, außer ich selbst würde spezielleren:

try: 
    # do something 
except IOError, e: 
    # ooops, check the attributes of e to see precisely what happened. 
    if e.errno != 23: 
     # I don't know how to handle this 
     raise 

wo „23“ ist die Fehlernummer, die Sie von EPIPE erhalten. Auf diese Weise versuchen Sie nicht, einen Berechtigungsfehler oder etwas anderes, für das Sie nicht ausgerüstet sind, zu behandeln.

+2

Das errno wäre 32, nicht 23. – mhawke

+2

Ich hätte klarstellen sollen, dass ich "23" als Platzhalter meinte. "Ja wirklich?" 32? Ich war näher als ich es erwartet hätte. :-) –

47

Angenommen, Sie verwenden das Standard-Socket-Modul, sollten Sie die Ausnahme socket.error: (32, 'Broken pipe') fangen (nicht IOError, wie andere vorgeschlagen haben). Dies wird in dem Fall ausgelöst, den Sie beschrieben haben, d. H. Senden/Schreiben an einen Socket, für den die entfernte Seite die Verbindung getrennt hat.

import socket, errno, time 

# setup socket to listen for incoming connections 
s = socket.socket() 
s.bind(('localhost', 1234)) 
s.listen(1) 
remote, address = s.accept() 

print "Got connection from: ", address 

while 1: 
    try: 
     remote.send("message to peer\n") 
     time.sleep(1) 
    except socket.error, e: 
     if isinstance(e.args, tuple): 
      print "errno is %d" % e[0] 
      if e[0] == errno.EPIPE: 
       # remote peer disconnected 
       print "Detected remote disconnect" 
      else: 
       # determine and handle different error 
       pass 
     else: 
      print "socket error ", e 
     remote.close() 
     break 
    except IOError, e: 
     # Hmmm, Can IOError actually be raised by the socket module? 
     print "Got IOError: ", e 
     break 

Beachten Sie, dass diese Ausnahme nicht immer auf den ersten Schreibvorgang in einer geschlossenen Buchse angehoben werden - mehr in der Regel die zweite Schreib (es sei denn, die Anzahl von Bytes in dem ersten Schreib geschrieben ist größer als die Puffergröße des Sockets). Sie müssen dies im Hinterkopf behalten, wenn Ihre Anwendung der Ansicht ist, dass das entfernte Ende die Daten vom ersten Schreibvorgang empfangen hat, obwohl es möglicherweise bereits getrennt wurde.

Sie können die Häufigkeit reduzieren (aber nicht vollständig eliminieren), indem Sie select.select() (oder poll) verwenden. Suchen Sie nach Daten, die bereit sind, vom Peer gelesen zu werden, bevor Sie einen Schreibvorgang durchführen. Wenn select meldet, dass Daten zum Lesen aus dem Peer-Socket verfügbar sind, lesen Sie es unter Verwendung von socket.recv(). Wenn dies eine leere Zeichenfolge zurückgibt, hat die Gegenstelle die Verbindung geschlossen. Da es hier immer noch eine Wettlaufsituation gibt, müssen Sie die Ausnahme dennoch fangen und behandeln.

Twisted ist großartig für diese Art von Sache, aber es klingt, als ob Sie bereits ein gutes Stück Code geschrieben haben.

+0

Das sieht komisch aus: if instance (e.args, tuple): '. Kann jemand das erklären? – guettli

+0

Das bedeutet, "ist e.args ein Tupel?" – mjz19910

0

Ich stelle mich mit der gleichen Frage. Aber ich gebe den gleichen Code das nächste Mal, es funktioniert einfach. Das erste Mal brach:

$ packet_write_wait: Connection to 10.. port 22: Broken pipe 

Das zweite Mal funktioniert es:

[1] Done     nohup python -u add_asc_dec.py > add2.log 2>&1 

Ich denke, der Grund, über die aktuelle Server-Umgebung sein kann.