Jak poradzić sobie ze złamaną rurą (SIGPIPE) w Pythonie?

Napisałem prosty wielowątkowy serwer gier w Pythonie, który tworzy nowy wątek dla każdego połączenia z klientem. Stwierdzam, że od czasu do czasu Serwer się zawiesza z powodu błędu broken-pipe / SIGPIPE. Jestem prawie pewien, że dzieje się tak, gdy program próbuje wysłać odpowiedź z powrotem do klienta, który nie jest już obecny.

Jaki jest dobry sposób, aby sobie z tym poradzić? Moim preferowanym rozwiązaniem byłoby po prostu zamknąć połączenie po stronie serwera z klientem i Kontynuuj, zamiast opuszczać cały program.

PS: to Pytanie/Odpowiedź dotyczy problemu w sposób ogólny; jak konkretnie powinienem go rozwiązać?

Author: Konrad Rudolph, 2008-10-07

5 answers

Czytaj na próbie: oświadczenie.

try:
    # do something
except socket.error, e:
    # A socket error
except IOError, e:
    if e.errno == errno.EPIPE:
        # EPIPE error
    else:
        # Other error
 40
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-14 20:45:02

Zakładając, że używasz standardowego modułu gniazd, powinieneś wyłapać wyjątek socket.error: (32, 'Broken pipe') (nie IOError, jak sugerowali inni). Zostanie to podniesione w przypadku, który opisałeś, tzn. wysyłanie/zapis do gniazda, dla którego strona zdalna się rozłączyła.

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

Zauważ, że ten wyjątek nie zawsze będzie wywoływany przy pierwszym zapisie do zamkniętego gniazda - zwykle drugi zapis (chyba że liczba bajtów zapisanych w pierwszym zapisie jest większa niż rozmiar bufora gniazda). Należy o tym pamiętać, jeśli aplikacja uzna, że zdalny koniec otrzymał dane z pierwszego zapisu, gdy mógł już się rozłączyć.

Można zmniejszyć częstość występowania (ale nie całkowicie wyeliminować) tego zjawiska za pomocą select.select() (lub poll). Przed przystąpieniem do zapisu sprawdź, czy dane są gotowe do odczytu z peera. Jeśli select zgłosi, że są dostępne dane do odczytania z gniazda peer, przeczytaj je za pomocą socket.recv(). Jeśli zwróci pusty łańcuch, zdalny peer zamknął połączenie. Ponieważ nadal istnieją warunki wyścigowe, nadal będziesz musiał złapać i obsłużyć wyjątek.

Twisted jest świetny do tego typu rzeczy, jednak wygląda na to, że napisałeś już sporo kodu.

 55
Author: mhawke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 02:23:50

SIGPIPE (chociaż myślę, że może masz na myśli EPIPE?) występuje na gniazdach po wyłączeniu gniazda, a następnie wysłaniu do niego danych. Prostym rozwiązaniem jest nie zamykanie gniazda przed próbą wysłania mu danych. Może się to zdarzyć również na rurach, ale nie brzmi to tak, jakbyś tego doświadczał, ponieważ jest to serwer sieciowy.

Możesz również po prostu zastosować plaster łapania wyjątku w jakimś najwyższego poziomu obsługi w każdym wątku.

Oczywiście, jeśli użyłeś Twisted zamiast tworzyć nowy wątek dla każdego połączenia z klientem, prawdopodobnie nie miałbyś tego problemu. To naprawdę trudne (może niemożliwe, w zależności od aplikacji), aby uporządkować operacje zamknięcia i zapisu poprawne, jeśli wiele wątków ma do czynienia z tym samym kanałem wejścia/Wyjścia.

 3
Author: Glyph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 20:55:36

Stoję przed tym samym pytaniem. Ale następnym razem wyślę ten sam kod, to po prostu działa. Pierwszy raz się zepsuł:

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

Po raz drugi działa:

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

Myślę, że powodem może być obecne środowisko serwera.

 -2
Author: yuan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-26 05:46:49

Moja odpowiedź jest bardzo zbliżona do S. Lotta, z tym, że byłbym bardziej konkretny:

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

Gdzie " 23 " jest numerem błędu, który otrzymujesz od EPIPE. W ten sposób nie będziesz próbował obsłużyć błędu uprawnień ani czegokolwiek innego, do czego nie jesteś przygotowany.

 -4
Author: Kirk Strauser,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-07 21:05:54