webentwicklung-frage-antwort-db.com.de

Wie vermeide ich SIGPIPEs (oder gehe richtig damit um)

Ich habe ein kleines Serverprogramm, das Verbindungen über einen TCP oder einen lokalen UNIX-Socket akzeptiert, einen einfachen Befehl liest und je nach Befehl eine Antwort sendet. Das Problem ist, dass der Client möglicherweise keinen hat Manchmal ist die Antwort interessant und wird vorzeitig beendet, daher führt das Schreiben auf diesen Socket zu einem SIGPIPE und zum Absturz meines Servers. Wie kann der Absturz hier am besten verhindert werden? (select () scheint hier nicht zu funktionieren, da immer angegeben wird, dass der Socket beschreibbar ist.) Oder sollte ich die SIGPIPE einfach mit einem Handler fangen und ignorieren?

239
jkramer

Im Allgemeinen möchten Sie SIGPIPE ignorieren und den Fehler direkt in Ihrem Code behandeln. Dies liegt daran, dass Signal-Handler in C viele Einschränkungen in Bezug auf ihre Funktionen haben.

Am einfachsten ist es, den SIGPIPE -Handler auf SIG_IGN Zu setzen. Dadurch wird verhindert, dass Socket- oder Pipe-Schreibvorgänge ein SIGPIPE -Signal auslösen.

Verwenden Sie den folgenden Code, um das Signal SIGPIPE zu ignorieren:

signal(SIGPIPE, SIG_IGN);

Wenn Sie den Aufruf send() verwenden, können Sie auch die Option MSG_NOSIGNAL Verwenden, mit der das Verhalten SIGPIPE auf Anrufbasis deaktiviert wird. Beachten Sie, dass nicht alle Betriebssysteme das Flag MSG_NOSIGNAL Unterstützen.

Schließlich möchten Sie möglicherweise auch das Socket-Flag SO_SIGNOPIPE Berücksichtigen, das bei einigen Betriebssystemen mit setsockopt() festgelegt werden kann. Dies verhindert, dass SIGPIPE durch Schreibvorgänge nur auf die Sockets verursacht wird, auf denen es festgelegt ist.

233
dvorak

Eine andere Methode besteht darin, den Socket so zu ändern, dass beim Schreiben () niemals SIGPIPE generiert wird. Dies ist in Bibliotheken praktischer, in denen Sie möglicherweise keinen globalen Signal-Handler für SIGPIPE benötigen.

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

In diesem Fall wird anstelle des SIGPIPE-Signals EPIPE zurückgegeben.

149
user55807

Ich komme sehr 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 gute Alternative, wenn Sie beispielsweise auf einem Linux-System ohne SO_NOSIGPIPE Arbeiten, ist das Setzen des Flags MSG_NOSIGNAL Bei Ihrem send (2) -Aufruf.

Beispiel für das Ersetzen von write(...) durch send(...,MSG_NOSIGNAL) (siehe nobar 's Kommentar)

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

In diesem post habe ich eine mögliche Lösung für den Solaris-Fall beschrieben, bei dem 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 ansteht. Wenn dies der Fall ist, bedeutet dies, dass es in diesem Thread blockiert ist und wir nichts tun müssen. Wenn die Bibliothek zusätzliches SIGPIPE generiert, wird es mit dem ausstehenden zusammengeführt, und das ist ein No-Op. Wenn SIGPIPE nicht ansteht, blockieren wir es in diesem Thread und prüfen auch, ob es bereits blockiert wurde. Dann können wir unsere Schreibvorgänge ausführen. Wenn Sie SIGPIPE in den ursprünglichen Zustand zurückversetzen möchten, gehen Sie wie folgt vor: Wenn SIGPIPE ursprünglich ausstehend war, werden keine Aktionen ausgeführt. Ansonsten prüfen wir, ob es jetzt ansteht. Wenn dies der Fall ist (was bedeutet, dass aus den Aktionen ein oder mehrere SIGPIPEs generiert wurden), warten wir in diesem Thread darauf und löschen den ausstehenden Status (dazu verwenden wir sigtimedwait () mit einer Zeitüberschreitung 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 ausstehend angezeigt, aber möglicherweise wird es von einem anderen Thread verarbeitet, bevor eine Änderung vorgenommen wurde, auf die gewartet werden musste. Nach dem Löschen des ausstehenden Status wird SIGPIPE in diesem Thread entsperrt, jedoch nur, wenn es ursprünglich nicht gesperrt war.

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

29
kroki

SIGPIPE lokal handhaben

In der Regel ist es am besten, den Fehler lokal und nicht in einem globalen Signal-Event-Handler zu behandeln, da Sie vor Ort mehr Kontext haben, was vor sich geht und welchen Rückgriff Sie nehmen müssen.

Ich habe eine Kommunikationsschicht in einer meiner Apps, mit der meine App mit einem externen Zubehör kommunizieren kann. Wenn ein Schreibfehler auftritt, wirf ich eine Ausnahme in die Kommunikationsschicht und lasse sie in einen Try-Catch-Block hochsprudeln, um dort damit umzugehen.

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. Bei dem Versuch, den Socket zu verwenden, wird jedoch ein Lese-/Schreibfehler angezeigt. Daher müssen Sie dies überprüfen.

22
Sam

Sie können nicht verhindern, dass der Prozess am anderen Ende einer Pipe beendet wird. Wenn er beendet wird, bevor Sie mit dem Schreiben fertig sind, erhalten Sie ein SIGPIPE-Signal. Wenn Sie das Signal SIG_IGN, wird Ihr Schreibvorgang mit einem Fehler zurückgegeben - und Sie müssen diesen Fehler notieren und darauf reagieren. Das Abfangen und Ignorieren des Signals in einem Handler ist keine gute Idee. Beachten Sie, dass die Pipe jetzt nicht mehr funktioniert, und ändern Sie das Verhalten des Programms, damit es nicht erneut in die Pipe schreibt (da das Signal erneut generiert und ignoriert wird) Nochmals, und Sie werden es noch einmal versuchen, und der gesamte Prozess könnte eine lange Zeit dauern und viel CPU-Leistung verschwenden.

15

Oder soll ich die SIGPIPE einfach mit einem Handler fangen und ignorieren?

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

Sam

6
Sam Reynolds

Was ist die beste Vorgehensweise, um den Absturz hier zu verhindern?

Deaktivieren Sie Sigpipes entweder wie gewohnt oder fangen Sie den Fehler ab und ignorieren Sie ihn.

Gibt es eine Möglichkeit zu überprüfen, ob die andere Seite der Zeile noch liest?

Ja, benutze select ().

select () scheint hier nicht zu funktionieren, da es immer besagt, dass der Socket beschreibbar ist.

Sie müssen an den Lesen Bits auswählen. Sie können wahrscheinlich die write Bits ignorieren.

Wenn die Gegenseite ihre Dateizugriffsnummer schließt, zeigt select an, dass Daten zum Lesen bereit sind. Wenn Sie das lesen, erhalten Sie 0 Bytes zurück, was bedeutet, dass das Datei-Handle geschlossen wurde.

Das einzige Mal, dass Sie die Schreibbits nicht ignorieren können, ist, wenn Sie große Volumes senden, und es besteht die Gefahr, dass das andere Ende überlastet wird, was dazu führen kann, dass sich Ihre Puffer füllen. In diesem Fall kann der Versuch, in das Dateihandle zu schreiben, dazu führen, dass Ihr Programm/Thread blockiert oder fehlschlägt. Das Testen von select vor dem Schreiben schützt Sie davor, garantiert jedoch nicht, dass das andere Ende fehlerfrei ist oder dass Ihre Daten ankommen.

Beachten Sie, dass Sie eine Sigpipe sowohl von close () als auch beim Schreiben erhalten können.

Close löscht alle gepufferten Daten. Wenn das andere Ende bereits geschlossen wurde, schlägt das Schließen fehl und Sie erhalten eine Sigpipe.

Wenn Sie gepuffertes TCPIP verwenden, bedeutet ein erfolgreicher Schreibvorgang nur, dass Ihre Daten zum Senden in die Warteschlange gestellt wurden. Dies bedeutet nicht, dass sie gesendet wurden. Bis Sie close erfolgreich anrufen, wissen Sie nicht, dass Ihre Daten gesendet wurden.

Sigpipe sagt dir, dass etwas schief gelaufen ist, es sagt dir nicht, was oder was du dagegen tun sollst.

3
Ben Aveling

Linux-Handbuch sagte:

EPIPE Das lokale Ende wurde an einem verbindungsorientierten Socket heruntergefahren. In diesem Fall erhält der Prozess auch ein SIGPIPE, sofern MSG_NOSIGNAL nicht gesetzt ist.

Aber für Ubuntu 12.04 ist es nicht richtig. Ich habe einen Test für diesen Fall geschrieben und erhalte immer EPIPE ohne SIGPIPE. SIGPIPE wird generiert, wenn ich zum zweiten Mal versuche, auf den gleichen kaputten Socket zu schreiben. Sie müssen SIGPIPE also nicht ignorieren, wenn dieses Signal auftritt, bedeutet dies einen logischen Fehler in Ihrem Programm.

3
talash

Unter einem modernen POSIX-System (d. H. Linux) können Sie die Funktion sigprocmask() verwenden.

#include <signal.h>

void block_signal(int signal_to_block /* i.e. SIGPIPE */ )
{
    sigset_t set;
    sigset_t old_state;

    // get the current state
    //
    sigprocmask(SIG_BLOCK, NULL, &old_state);

    // add signal_to_block to that existing state
    //
    set = old_state;
    sigaddset(&set, signal_to_block);

    // block that signal also
    //
    sigprocmask(SIG_BLOCK, &set, NULL);

    // ... deal with old_state if required ...
}

Wenn Sie den vorherigen Status später wiederherstellen möchten, müssen Sie den old_state An einem sicheren Ort speichern. Wenn Sie diese Funktion mehrmals aufrufen, müssen Sie entweder einen Stack verwenden oder nur den ersten oder letzten old_state Speichern. Oder Sie haben eine Funktion, die ein bestimmtes blockiertes Signal entfernt.

Weitere Informationen finden Sie in der Manpage .

2
Alexis Wilke