Ich habe ein kleines Serverprogramm, das Verbindungen über einen TCP- oder lokalen UNIX-Socket akzeptiert, einen einfachen Befehl liest und je nach Befehl eine Antwort sendet. Das Problem ist, dass der Client manchmal kein Interesse an der Antwort hat und vorzeitig beendet wird. Wenn Sie also in diesen Socket schreiben, wird ein SIGPIPE ausgelöst und mein Server stürzt ab. Was ist die beste Vorgehensweise, um den Absturz hier zu verhindern? Gibt es eine Möglichkeit zu überprüfen, ob die andere Seite der Zeile noch liest? (select () scheint hier nicht zu funktionieren, da immer gesagt wird, dass der Socket beschreibbar ist). Oder sollte ich das SIGPIPE einfach mit einem Handler fangen und ignorieren?
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 Signalhandler für SIGPIPE benötigen.
Auf den meisten BSD-basierten Systemen (MacOS, FreeBSD ...) (vorausgesetzt, Sie verwenden C / C ++) können Sie dies folgendermaßen tun:
Damit wird anstelle des generierten SIGPIPE-Signals EPIPE zurückgegeben.
quelle
Ich bin super spät zur Party, aber
SO_NOSIGPIPE
nicht portabel und funktioniert möglicherweise nicht auf Ihrem System (es scheint eine BSD-Sache zu sein).Eine gute Alternative, wenn Sie beispielsweise ein Linux-System ohne verwenden
SO_NOSIGPIPE
, besteht darin, dasMSG_NOSIGNAL
Flag bei Ihrem send (2) -Aufruf zu setzen.Beispiel ersetzen
write(...)
durchsend(...,MSG_NOSIGNAL)
(siehe Kommentar von Nobar )quelle
QTcpSocket
Objekt, um die Verwendung von Sockets zu verpacken. Ich musste denwrite
Methodenaufruf durch ein Betriebssystem ersetzensend
(mithilfe dersocketDescriptor
Methode). Kennt jemand eine sauberere Option, um diese Option in einerQTcpSocket
Klasse festzulegen?In diesem Beitrag habe ich eine mögliche Lösung für den Solaris-Fall beschrieben, wenn weder SO_NOSIGPIPE noch MSG_NOSIGNAL verfügbar sind.
Beispielcode unter https://github.com/kroki/XProbes/blob/1447f3d93b6dbf273919af15e59f35cca58fcc23/src/libxprobes.c#L156
quelle
SIGPIPE lokal handhaben
Es ist normalerweise am besten, den Fehler lokal und nicht in einem globalen Signalereignishandler zu behandeln, da Sie lokal mehr Kontext darüber haben, was vor sich geht und welchen Rückgriff Sie nehmen 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 aufblasen, um sie dort zu behandeln.
Code:
Der Code zum Ignorieren eines SIGPIPE-Signals, damit Sie es lokal verarbeiten können, lautet:
Dieser Code verhindert, dass das SIGPIPE-Signal ausgelöst wird. Beim Versuch, den Socket zu verwenden, wird jedoch ein Lese- / Schreibfehler angezeigt. Daher müssen Sie dies überprüfen.
quelle
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. Nur das Signal in einem Handler abzufangen und zu ignorieren, ist keine gute Idee. 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 (da das Signal erneut generiert und ignoriert wird erneut, und Sie werden es erneut versuchen, und der gesamte Vorgang kann lange dauern und viel CPU-Leistung verschwenden.
quelle
Ich glaube das ist richtig. Sie möchten wissen, wann das andere Ende den Deskriptor geschlossen hat, und das sagt Ihnen SIGPIPE.
Sam
quelle
Das Linux-Handbuch sagte:
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 beim zweiten Mal versuche, in denselben defekten Socket zu schreiben. Sie müssen SIGPIPE also nicht ignorieren, wenn dieses Signal auftritt. Dies bedeutet einen logischen Fehler in Ihrem Programm.
quelle
Deaktivieren Sie entweder die Sigpipes für alle oder fangen Sie den Fehler ab und ignorieren Sie ihn.
Ja, verwenden Sie select ().
Sie müssen sich auf die Auswahl lesen Bits. Sie können die Schreibbits wahrscheinlich ignorieren .
Wenn das entfernte Ende sein Dateihandle schließt, zeigt Ihnen select an, dass Daten zum Lesen bereit sind. Wenn Sie das lesen, erhalten Sie 0 Bytes zurück. Auf diese Weise teilt Ihnen das Betriebssystem mit, dass das Dateihandle geschlossen wurde.
Das einzige Mal, wenn 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 eintreffen.
Beachten Sie, dass Sie eine Sigpipe sowohl von close () als auch beim Schreiben erhalten können.
Durch Schließen werden alle gepufferten Daten gelöscht. Wenn das andere Ende bereits geschlossen wurde, schlägt das Schließen fehl und Sie erhalten ein 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 erfolgreich close anrufen, wissen Sie nicht, dass Ihre Daten gesendet wurden.
Sigpipe sagt Ihnen, dass etwas schief gelaufen ist, es sagt Ihnen nicht, was oder was Sie dagegen tun sollten.
quelle
Unter einem modernen POSIX-System (dh Linux) können Sie die
sigprocmask()
Funktion verwenden.Wenn Sie den vorherigen Status später wiederherstellen möchten, stellen Sie sicher, dass Sie ihn an einem
old_state
sicheren Ort aufbewahren. Wenn Sie diese Funktion mehrmals aufrufen, müssen Sie entweder einen Stapel verwenden oder nur den ersten oder letzten speichernold_state
... oder eine Funktion haben, die ein bestimmtes blockiertes Signal entfernt.Weitere Informationen finden Sie auf der Manpage .
quelle
sigprocmask
Fügt den Satz blockierter Signale hinzu, sodass Sie dies alles mit einem einzigen Anruf erledigen können.old_state
damit ich ihn später wiederherstellen kann, wenn ich möchte. Wenn Sie wissen, dass Sie den Status niemals wiederherstellen müssen, müssen Sie ihn auf diese Weise nicht lesen und speichern.sigprocmask()
Lesen des alten Status, der Aufruf zumsigaddset
Ändern, der zweite Aufruf zumsigprocmask()
Schreiben. Sie können den ersten Anruf entfernen, mit initialisierensigemptyset(&set)
und den zweiten Anruf in ändernsigprocmask(SIG_BLOCK, &set, &old_state)
.