12

Wenn Sie einen geschlossenen TCP-Socket lesen, erhalten Sie einen regulären Fehler, d. H. Es gibt 0 entweder EOF oder -1 zurück und einen Fehlercode in errno, der mit perror gedruckt werden kann.Warum ist das Schreiben eines geschlossenen TCP-Sockels schlimmer als das Lesen eines?

Wenn Sie jedoch einen geschlossenen TCP-Socket schreiben, sendet das Betriebssystem SIGPIPE an Ihre App, wodurch die App beendet wird, wenn sie nicht abgefangen wird.

Warum ist das Schreiben der geschlossenen TCP-Buchse schlimmer als das Lesen?

+0

Es gibt etwas anderes hier, das ziemlich subtil ist: Eine TCP-Verbindung kann halb geschlossen sein, dh eine Seite hat den Socket geschlossen (ein FIN-Paket gesendet), aber die andere Seite hat noch Daten zu senden. Wenn Sie auf dieser Ebene herumwühlen, lesen Sie bitte: http://superuser.com/questions/298919/what-is-tcp-half-open-connection-and-tcp-half-closed-connection – rbp

Antwort

12

+1 Zu Greg Hewgill für die Leitung meines Gedankenprozesses in die richtige Richtung, um die Antwort zu finden.

Der eigentliche Grund für SIGPIPE in Sockets und Pipes ist das Filter-Idiom/-Muster, das für typische E/A in Unix-Systemen gilt.

Beginnend mit Rohren. Filterprogramme wie grep schreiben normalerweise in STDOUT und lesen von STDIN, die von der Shell zu einer Pipe umgeleitet werden können. Zum Beispiel:

cat someVeryBigFile | grep foo | doSomeThingErrorProne 

Die Shell, wenn er sich gabelt und dann exec diese Programme wahrscheinlich verwendet das dup2 Systemaufruf STDIN, STDOUT und STDERR an die entsprechenden Leitungen umleiten.

Da das Filterprogramm grep nicht weiß, und hat keine Möglichkeit, zu wissen, dass es Ausgang dann den einzigen Weg umgeleitet wurde, es zu sagen, zu einem Rohrbruch zu stoppen zu schreiben, wenn doSomeThingErrorProne Abstürze mit einem Signal, da die Rückgabewerte sind von Schreiben an STDOUT werden selten, wenn überhaupt überprüft.

Das Analog mit Sockets wäre der inetd Server, der den Platz der Shell übernimmt.

Als Beispiel nehme ich an, Sie könnten grep in einen Netzwerkdienst verwandeln, der über TCP Sockets funktioniert. Zum Beispiel mit inetd möchten, wenn Sie einen grep Server auf TCP Port 8000 dann fügen Sie diese /etc/services haben:

grep  8000/tcp # grep server 

Dann fügen Sie diese /etc/inetd.conf:

grep stream tcp nowait root /usr/bin/grep grep foo 

senden SIGHUP-inetd und eine Verbindung zum Port 8000 mit Telnet. Dies sollte dazu führen inetd zu fork, dup den Sockel auf STDIN, STDOUT und STDERR und dann exec grep mit foo als Argument. Wenn Sie beginnen, Zeilen in Telnet grep eingeben, werden die Zeilen, die foo enthalten.

Jetzt ersetzen telnet mit einem Programm namens ticker, dass zum Beispiel einen Strom von Echtzeit Aktienkurse auf STDOUT und bekommt Befehle auf STDIN schreibt.Jemand telnet an Port 8000 und gibt "start java" ein, um Angebote für Sun Microsystems zu erhalten. Dann stehen sie auf und gehen zum Mittagessen. Telnet stürzt unerklärlicherweise ab. Wenn es keine SIGPIPE zu senden gab, würde ticker weiterhin für immer Zitate senden, nie wissend, dass der Prozess am anderen Ende abgestürzt war und Systemressourcen unnötig verschwendet.

10

Wenn Sie in eine Steckdose schreiben, würden Sie normalerweise erwarten, dass das andere Ende zuhört. Das ist wie ein Telefonanruf - wenn Sie sprechen, würden Sie nicht erwarten, dass die andere Partei den Anruf einfach auflegt.

Wenn Sie von einem Socket lesen, dann erwarten Sie, dass das andere Ende entweder (a) Ihnen etwas sendet oder (b) den Socket schließt. Situation (b) würde passieren, wenn Sie gerade etwas wie einen QUIT-Befehl an das andere Ende gesendet haben.

+0

Aber das sagt mir nicht wirklich, warum 'write' oder' send' nicht einfach einen Fehler auf die gleiche Art und Weise zurückbringen können, wie 'read' oder' recv'. Warum mit "SIGPIPE" so die App über den Kopf schlagen? Es muss einen tieferen Grund für eine so extreme Reaktion des OS geben. Sagen wir, ich habe einen Sockel, der gerade eine 'RST' empfangen hat. Wenn ich es "lese", bekomme ich -1 mit ECONNRESET, warum bekomme ich nicht dasselbe, wenn ich schreibe? In beiden Fällen erwarte ich einvernehmliche I/O und bekomme nicht das, was ich erwartet habe. –

+6

@Robert: Der typische Anwendungsfall für die Eingabe und Ausgabe von Pipes unter Unix war historisch für "Filter" -Programme, die von einer Eingangsleitung lesen und in eine Ausgangsleitung schreiben (das 'grep' Programm ist ein solches Beispiel). Damit ein solcher Filter sofort beendet wird, wenn der Ausgang nicht mehr abhört, wurde das Standardverhalten des Signals "SIGPIPE" eingestellt, um das Programm zu beenden. Ohne dies würde der Filter mit dem Schreiben fortfahren, bis seine Eingabe erschöpft war (was eine Weile dauern könnte). –

+2

Sag mir, ob das richtig klingt: Der wahre Grund für 'SIGPIPE' ist, dass Filterprogramme wie grep normalerweise in' STDOUT' schreiben, was von der Shell zu einer Pipe umgeleitet werden kann. Da das Filterprogramm nicht weiß und nicht wissen kann, dass seine Ausgabe umgeleitet wurde, ist die einzige Möglichkeit, es zu beenden, in eine unterbrochene Pipe zu schreiben, ein Signal, da Rückgabewerte von Schreibvorgängen in 'STDOUT' selten überprüft werden . Das Analog mit den Sockets wäre 'inetd', das eine Verbindung akzeptiert, den Server erzeugt und' dup2' den Socket auf 'STDIN',' STDOUT', 'STDERR' setzt! –

3

Ich denke, ein großer Teil der Antwort ist "so, dass eine Buchse verhält sich eher ähnlich wie eine klassische Unix (anonyme) Rohr". Diese zeigen auch das gleiche Verhalten - bezeugen Sie den Namen des Signals.

Also, dann ist es vernünftig zu fragen, warum sich Rohre so verhalten. Greg Hewgills Antwort gibt eine Zusammenfassung der Situation.

Eine andere Betrachtungsweise ist - was ist die Alternative? Sollte ein 'read()' auf einem Rohr ohne Schreiber ein SIGPIPE Signal geben? Die Bedeutung von SIGPIPE müsste natürlich von "schreibe auf eine Pipe mit niemandem, um es zu lesen" geändert werden, aber das ist trivial. Es gibt keinen besonderen Grund zu denken, dass es besser wäre; Die EOF-Anzeige (Null Byte zu lesen; Null Byte gelesen) ist eine perfekte Beschreibung des Zustandes der Pipe, und daher ist das Leseverhalten gut.

Was ist mit 'write()'? Nun, eine Option wäre, die Anzahl der geschriebenen Bytes zurückzugeben - null. Aber das ist keine gute Idee; Es impliziert, dass der Code es erneut versuchen sollte und möglicherweise mehr Bytes gesendet werden, was nicht der Fall sein wird. Eine andere Option wäre ein Fehler - write() gibt -1 zurück und setzt ein entsprechendes errno. Es ist nicht klar, dass es einen gibt. EINVAL oder EBADF sind beide ungenau: Der Dateideskriptor ist korrekt und offen an diesem Ende (und sollte nach dem fehlerhaften Schreiben geschlossen werden); Es gibt einfach nichts, um es zu lesen. EPIPE bedeutet "gebrochene PIPE"; Mit einem Vorbehalt zu "Dies ist ein Sockel, kein Rohr" wäre das der richtige Fehler. Es ist wahrscheinlich das errno zurückgegeben, wenn Sie SIGPIPE ignorieren. Es wäre möglich, dies zu tun - einfach einen entsprechenden Fehler zurückgeben, wenn die Leitung unterbrochen ist (und niemals das Signal senden). Es ist jedoch eine empirische Tatsache, dass viele Programme nicht so sehr darauf achten, wohin ihre Ausgabe geht, und wenn Sie einen Befehl, der eine Multi-Gigabyte-Datei liest, in einen Prozess pipettieren, der nach den ersten 20 KB beendet wird achtet nicht auf den Status seiner Schreibvorgänge, dann wird es eine lange Zeit dauern, um zu beenden, und wird dabei Maschinenaufwand verschwenden, während durch das Senden eines Signals, dass es nicht ignoriert, wird es schnell aufhören - dies ist definitiv vorteilhaft. Und Sie können den Fehler erhalten, wenn Sie es wollen. So hat das Senden von Signalen Vorteile für die o/s im Zusammenhang mit Rohren; und Buchsen emulieren Pfeifen ziemlich genau.

Interessante beiseite: Während die Nachricht für SIGPIPE Überprüfung, fand ich die Socket-Option:

#define SO_NOSIGPIPE 0x1022 /* APPLE: No SIGPIPE on EPIPE */ 
+0

Also im Grunde sagen Sie, dass 'SIGPIPE' existiert, weil viele Programmierer Fehlercodes im Falle des Schreibens ignorieren, was dazu führen könnte, dass der Prozess Systemressourcen beschlagnahmt, wenn er gar nichts leistet? Oder anders ausgedrückt, die Leute sind sorgfältiger bei der Überprüfung ihrer Eingaben als bei der Ausgabe, und das ist der Grund für die Asymmetrie in "lesen" und "schreiben"? –

+0

@Robert: Ja, im Grunde genommen. Menschen neigen dazu, ihren Code unter der Annahme zu schreiben, dass das Ausgabegerät nicht verloren geht oder keinen Platz mehr hat. Wenn die Ausgabe eine Pipe ist und das empfangende Programm vor dem Ende der Ausgabe aufhört zu lesen, ist es wichtig sicherzustellen, dass das Schreibprogramm beachtet wird. Und dies ist ein einfacher Mechanismus, der Programme einfacher schreiben lässt. –

+0

Gab es also eine Zeit vor "SIGPIPE"? Da Sie sagen, dass dies bis zu einem gewissen Grad ein Ergebnis von schlechtem Verhalten von Benutzern/Programmierern ist, gab es einmal eine Version von Unix, die einen Fehler beim Schreiben in eine geschlossene Pipe zurückgab und dann änderte sie ein Signal zurück, oder war 'SIGPIPE 'da von Anfang an im Vorgriff auf schlechtes Benehmen? –

7

Denken Sie an die Buchse als eine große Pipeline von Daten zwischen dem Sende- und dem Empfangsprozess. Stellen Sie sich nun vor, dass die Rohrleitung ein Ventil hat, das geschlossen ist (die Muffenverbindung ist geschlossen).

Wenn Sie von der Steckdose lesen (versuchen, etwas aus der Leitung zu bekommen), ist es nicht schaden zu versuchen, etwas zu lesen, das nicht da ist; Sie werden nur keine Daten erhalten. Tatsächlich können Sie, wie Sie sagten, ein EOF bekommen, was korrekt ist, da es keine Daten mehr zu lesen gibt.

Allerdings schreiben zu dieser geschlossenen Verbindung ist eine andere Sache. Die Daten werden nicht durchgehen, und Sie könnten einige wichtige Nachrichten auf den Boden fallen lassen. (Man kann kein Wasser mit geschlossenem Ventil durch ein Rohr schicken; wenn du es versuchst, wird wahrscheinlich irgendwo etwas platzen, oder der Gegendruck sprüht überall Wasser.) Deshalb gibt es einen stärkeren Werkzeug, um Sie auf diesen Zustand aufmerksam zu machen, nämlich das SIGPIPE-Signal.

Sie können das Signal immer ignorieren oder blockieren, aber Sie tun dies auf eigene Gefahr.

Verwandte Themen