Wie man SIGPIPEs verhindert (oder richtig damit umgeht)

Lesezeit: 9 Minuten

Benutzeravatar von jkramer
jkramer

Ich habe ein kleines Serverprogramm, das Verbindungen auf einem TCP- oder lokalen UNIX-Socket akzeptiert, einen einfachen Befehl liest und (je nach Befehl) eine Antwort sendet.

Das Problem ist, dass der Kunde möglicherweise kein Interesse an der Antwort hat und manchmal vorzeitig abbricht. Das Schreiben in diesen Socket führt also zu a SIGPIPE und meinen Server zum Absturz bringen.

Was ist die beste Vorgehensweise, um den Absturz hier zu verhindern? Gibt es eine Möglichkeit zu überprüfen, ob die andere Seite der Leitung noch liest? (select() scheint hier nicht zu funktionieren, da es immer sagt, dass der Socket beschreibbar ist). Oder soll ich die einfach fangen SIGPIPE mit einem Handler und ignoriere es?

Benutzeravatar von dvorak
dvorak

Sie möchten die im Allgemeinen ignorieren SIGPIPE und behandeln Sie den Fehler direkt in Ihrem Code. Dies liegt daran, dass Signal-Handler in C viele Einschränkungen hinsichtlich ihrer Möglichkeiten haben.

Der portabelste Weg, dies zu tun, besteht darin, die SIGPIPE Handler zu SIG_IGN. Dadurch wird verhindert, dass ein Socket- oder Pipe-Schreibvorgang a verursacht SIGPIPE Signal.

Das zu ignorieren SIGPIPE verwenden Sie den folgenden Code:

signal(SIGPIPE, SIG_IGN);

Wenn Sie die verwenden send() anrufen, eine weitere Option ist die Verwendung der MSG_NOSIGNAL Option, die die drehen wird SIGPIPE Verhalten pro Anruf ab. Beachten Sie, dass nicht alle Betriebssysteme das unterstützen MSG_NOSIGNAL Flagge.

Schließlich möchten Sie vielleicht auch die berücksichtigen SO_SIGNOPIPE Socket-Flag, das mit gesetzt werden kann setsockopt() auf einigen Betriebssystemen. Dies wird verhindert SIGPIPE nicht durch Schreibzugriffe nur auf die Sockets verursacht werden, auf die es gesetzt ist.

  • Um dies explizit zu machen: signal(SIGPIPE, SIG_IGN);

    – jhclark

    4. März 2012 um 18:38 Uhr

  • Dies kann erforderlich sein, wenn Sie einen Exit-Rückgabecode von 141 (128 + 13:SIGPIPE) erhalten. SIG_IGN ist der Ignore-Signal-Handler.

    – Klettverschluss

    8. November 2012 um 20:51 Uhr


  • Für Sockets ist es wirklich einfacher, send() mit MSG_NOSIGNAL zu verwenden, wie @talash sagte.

    – Pawel Weselow

    14. Mai 2014 um 22:25 Uhr

  • Was genau passiert, wenn mein Programm in eine defekte Pipe (in meinem Fall einen Socket) schreibt? SIG_IGN lässt das Programm die SIG_PIPE ignorieren, aber führt das dann dazu, dass send() etwas Unerwünschtes tut?

    – sudo

    22. Mai 2014 um 22:16 Uhr

  • Erwähnenswert, da ich es einmal gefunden, später vergessen und verwirrt habe, dann ein zweites Mal entdeckt habe. Debugger (ich weiß, dass gdb dies tut) setzen Ihre Signalbehandlung außer Kraft, sodass ignorierte Signale nicht ignoriert werden! Testen Sie Ihren Code außerhalb eines Debuggers, um sicherzustellen, dass das SIGPIPE nicht mehr auftritt. stackoverflow.com/questions/6821469/…

    – Jetski S-Typ

    19. Januar 2016 um 6:08 Uhr


Benutzeravatar von user55807
Benutzer55807

Eine andere Methode besteht darin, den Socket so zu ändern, dass er bei write() niemals SIGPIPE generiert. Dies ist bequemer in Bibliotheken, wo Sie vielleicht keinen globalen Signal-Handler für SIGPIPE wollen.

Auf den meisten BSD-basierten (MacOS, FreeBSD …) Systemen (vorausgesetzt, Sie verwenden C/C++) können Sie dies tun mit:

int set = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));

Damit wird anstelle des erzeugten SIGPIPE-Signals EPIPE zurückgesendet.

  • Das hört sich gut an, aber SO_NOSIGPIPE scheint unter Linux nicht zu existieren – also ist es keine allgemein tragbare Lösung.

    – Brent Bradburn

    2. Dezember 2010 um 7:26 Uhr

  • Hallo zusammen, können Sie mir sagen, wo ich diesen Code einfügen muss? ich verstehe nicht danke

    – R. Dewi

    12. September 2011 um 11:55 Uhr

  • Unter Linux sehe ich die Option MSG_NOSIGNAL in Flags von senden

    – Aadishri

    3. Oktober 2013 um 7:45 Uhr

  • Funktioniert perfekt unter Mac OS X

    – Kirugan

    8. März 2015 um 11:44 Uhr

  • Wie setze ich dieses Flag, dass es für Sockets und Pipes funktioniert und keine Fehlermeldung erhalte, wenn ich es auf eine Datei setze?

    – 12431234123412341234123

    3. August 2021 um 15:51 Uhr

Benutzeravatar von sklnd
sknd

Ich bin super spät zur Party, aber SO_NOSIGPIPE ist nicht portabel und funktioniert möglicherweise nicht auf Ihrem System (es scheint eine BSD-Sache zu sein).

Eine schöne Alternative, wenn Sie beispielsweise auf einem Linux-System ohne sind SO_NOSIGPIPE wäre das einzustellen MSG_NOSIGNAL Flag bei Ihrem send(2)-Aufruf.

Beispiel ersetzen write(...) durch send(...,MSG_NOSIGNAL) (siehe nobars Kommentar)

char buf[888];
//write( sockfd, buf, sizeof(buf) );
send(    sockfd, buf, sizeof(buf), MSG_NOSIGNAL );

  • Mit anderen Worten, verwenden Sie send(…,MSG_NOSIGNAL) als Ersatz für write() und Sie erhalten kein SIGPIPE. Dies sollte für Sockets (auf unterstützten Plattformen) gut funktionieren, aber send() scheint auf die Verwendung mit Sockets (nicht Pipes) beschränkt zu sein, daher ist dies keine allgemeine Lösung für das SIGPIPE-Problem.

    – Brent Bradburn

    25. Januar 2011 um 17:19 Uhr

  • @Ray2k Wer um alles in der Welt entwickelt noch Anwendungen für Linux < 2.2? Das ist eigentlich eine halb ernste Frage; Die meisten Distributionen werden mit mindestens 2.6 ausgeliefert.

    – Partherschuss

    12. Juni 2014 um 17:54 Uhr

  • @Parthian Shot: Sie sollten Ihre Antwort überdenken. Die Wartung alter eingebetteter Systeme ist ein triftiger Grund, sich um ältere Linux-Versionen zu kümmern.

    – kirsche40

    20. August 2014 um 12:27 Uhr

  • @kirsche40 Ich bin nicht der OP – ich war nur neugierig.

    – Partherschuss

    3. September 2014 um 23:01 Uhr

  • @sklnd das hat bei mir perfekt funktioniert. Ich verwende ein Qt QTcpSocket Objekt zu Wrap-Sockets-Nutzung, musste ich ersetzen write Methodenaufruf durch ein Betriebssystem send (unter Verwendung socketDescriptor Methode). Kennt jemand eine sauberere Option, um diese Option in a QTcpSocket Klasse?

    – Emilio González Montaña

    5. Februar 2018 um 9:05 Uhr


Benutzeravatar von kroki
kroki

In diesem Post Ich habe eine mögliche Lösung für den Solaris-Fall beschrieben, wenn weder SO_NOSIGPIPE noch MSG_NOSIGNAL verfügbar sind.

Stattdessen müssen wir SIGPIPE im aktuellen Thread, der Bibliothekscode ausführt, vorübergehend unterdrücken. So geht’s: Um SIGPIPE zu unterdrücken, prüfen wir zuerst, ob es anhängig ist. Wenn dies der Fall ist, bedeutet dies, dass er in diesem Thread blockiert ist und wir nichts unternehmen müssen. Wenn die Bibliothek zusätzliche SIGPIPE generiert, wird sie mit der ausstehenden zusammengeführt, und das ist ein No-Op. Wenn SIGPIPE nicht anhängig ist, blockieren wir es in diesem Thread und prüfen auch, ob es bereits blockiert wurde. Dann können wir unsere Schreibvorgänge frei ausführen. Wenn wir SIGPIPE in seinen ursprünglichen Zustand zurückversetzen sollen, gehen wir wie folgt vor: Wenn SIGPIPE ursprünglich anhängig war, tun wir nichts. Andernfalls prüfen wir, ob es jetzt anhängig ist. Wenn dies der Fall ist (was bedeutet, dass unsere Aktionen ein oder mehrere SIGPIPEs generiert haben), dann warten wir in diesem Thread darauf und löschen so seinen schwebenden Status (dazu verwenden wir sigtimedwait() mit einem Timeout von null; dies dient dazu, ein Blockieren zu vermeiden ein Szenario, in dem ein böswilliger Benutzer SIGPIPE manuell an einen ganzen Prozess gesendet hat: In diesem Fall wird es als ausstehend angezeigt, aber ein anderer Thread kann damit umgehen, bevor wir eine Änderung hatten, um darauf zu warten). Nach dem Löschen des ausstehenden Status entsperren wir SIGPIPE in diesem Thread, aber nur, wenn es ursprünglich nicht blockiert war.

Beispielcode unter https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156

Behandeln Sie SIGPIPE lokal

Normalerweise ist es am besten, den Fehler lokal statt in einem globalen Signal-Event-Handler zu behandeln, da Sie lokal mehr Kontext darüber haben, was vor sich geht und welche Abhilfemaßnahmen ergriffen werden müssen.

Ich habe eine Kommunikationsschicht in einer meiner Apps, die es meiner App ermöglicht, mit einem externen Zubehör zu kommunizieren. Wenn ein Schreibfehler auftritt, werfe ich eine Ausnahme in die Kommunikationsschicht und lasse sie zu einem Try-Catch-Block hochsprudeln, um sie dort zu behandeln.

Code:

Der Code zum Ignorieren eines SIGPIPE-Signals, damit Sie es lokal verarbeiten können, lautet:

// We expect write failures to occur but we want to handle them where 
// the error occurs rather than in a SIGPIPE handler.
signal(SIGPIPE, SIG_IGN);

Dieser Code verhindert, dass das SIGPIPE-Signal ausgelöst wird, aber Sie erhalten einen Lese-/Schreibfehler, wenn Sie versuchen, den Socket zu verwenden, also müssen Sie das überprüfen.

  • Soll dieser Aufruf nach dem Anschließen einer Steckdose erfolgen oder davor? wo ist es am besten zu platzieren. Vielen Dank

    – Ahmed

    14. Februar 2013 um 2:14 Uhr

  • @ Ahmed Ich habe es persönlich eingefügt AnwendungDidFinishLaunching:. Die Hauptsache ist, dass es vor der Interaktion mit einer Socket-Verbindung sein sollte.

    – Sam

    14. Februar 2013 um 4:53 Uhr


  • Sie erhalten jedoch einen Lese-/Schreibfehler, wenn Sie versuchen, den Socket zu verwenden, also müssen Sie dies überprüfen. – Darf ich fragen, wie das möglich ist? Signal (SIGPIPE, SIG_IGN) funktioniert bei mir, aber im Debug-Modus gibt es einen Fehler zurück. Ist es möglich, diesen Fehler auch zu ignorieren?

    – n0min

    3. Oktober 2013 um 1:56 Uhr

  • @ Dreyfus15 Ich glaube, ich habe gerade Anrufe mit dem Socket in einen Try / Catch-Block gepackt, um ihn lokal zu verarbeiten. Es ist schon eine Weile her, dass ich das gesehen habe.

    – Sam

    3. Oktober 2013 um 14:36 ​​Uhr

Benutzeravatar von Jonathan Leffler
Jonathan Leffler

Sie können nicht verhindern, dass der Prozess am anderen Ende einer Pipe beendet wird, und wenn er beendet wird, bevor Sie mit dem Schreiben fertig sind, erhalten Sie ein SIGPIPE-Signal. Wenn Sie das Signal SIG_IGN ausführen, wird Ihr Schreibvorgang mit einem Fehler zurückgegeben – und Sie müssen diesen Fehler notieren und darauf reagieren. Es ist keine gute Idee, das Signal in einem Handler einfach abzufangen und zu ignorieren – Sie müssen beachten, dass die Pipe jetzt nicht mehr funktioniert, und das Verhalten des Programms so ändern, dass es nicht erneut in die Pipe schreibt (weil das Signal erneut generiert und ignoriert wird). erneut, und Sie werden es erneut versuchen, und der gesamte Prozess könnte für eine Weile fortgesetzt werden lang Zeit und viel CPU-Leistung verschwenden).

  • Soll dieser Aufruf nach dem Anschließen einer Steckdose erfolgen oder davor? wo ist es am besten zu platzieren. Vielen Dank

    – Ahmed

    14. Februar 2013 um 2:14 Uhr

  • @ Ahmed Ich habe es persönlich eingefügt AnwendungDidFinishLaunching:. Die Hauptsache ist, dass es vor der Interaktion mit einer Socket-Verbindung sein sollte.

    – Sam

    14. Februar 2013 um 4:53 Uhr


  • Sie erhalten jedoch einen Lese-/Schreibfehler, wenn Sie versuchen, den Socket zu verwenden, also müssen Sie dies überprüfen. – Darf ich fragen, wie das möglich ist? Signal (SIGPIPE, SIG_IGN) funktioniert bei mir, aber im Debug-Modus gibt es einen Fehler zurück. Ist es möglich, diesen Fehler auch zu ignorieren?

    – n0min

    3. Oktober 2013 um 1:56 Uhr

  • @ Dreyfus15 Ich glaube, ich habe gerade Anrufe mit dem Socket in einen Try / Catch-Block gepackt, um ihn lokal zu verarbeiten. Es ist schon eine Weile her, dass ich das gesehen habe.

    – Sam

    3. Oktober 2013 um 14:36 ​​Uhr

Benutzeravatar von Sam Reynolds
Sam Reynolds

Oder sollte ich das SIGPIPE einfach mit einem Hundeführer fangen und es ignorieren?

Ich glaube, das ist richtig. Sie möchten wissen, wann das andere Ende seinen Deskriptor geschlossen hat, und das sagt Ihnen SIGPIPE.

Sam

1426780cookie-checkWie man SIGPIPEs verhindert (oder richtig damit umgeht)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy