Close vs Shutdown Socket?

214

In C habe ich verstanden, dass wenn wir einen Socket schließen, dies bedeutet, dass der Socket zerstört wird und später wiederverwendet werden kann.

Wie wäre es mit Herunterfahren? Die Beschreibung besagt, dass die Hälfte einer Duplexverbindung zu diesem Socket geschlossen wird. Aber wird dieser Socket wie closeein Systemaufruf zerstört?

Tshepang
quelle
Es kann später nicht wiederverwendet werden. Es ist geschlossen. Fertig. Getan.
Marquis von Lorne

Antworten:

191

Dies wird im Netzwerkhandbuch von Beej erläutert . shutdownist eine flexible Möglichkeit, die Kommunikation in eine oder beide Richtungen zu blockieren. Wenn der zweite Parameter ist SHUT_RDWR, blockiert er sowohl das Senden als auch das Empfangen (wie close). Dies closeist jedoch der Weg, um eine Steckdose tatsächlich zu zerstören.

Mit shutdownkönnen Sie weiterhin ausstehende Daten empfangen, die der Peer bereits gesendet hat (danke an Joey Adams, der dies bemerkt hat).

Matthew Flaschen
quelle
23
Beachten Sie, dass selbst wenn Sie einen TCP-Socket schließen (), dieser ohnehin nicht sofort wiederverwendbar ist, da er sich in einem TIME_WAIT-Status befindet, während das Betriebssystem sicherstellt, dass keine ausstehenden Pakete vorhanden sind, die bei Ihnen als neue Informationen verwechselt werden könnten sollten diese Steckdose sofort für etwas anderes wiederverwenden.
Alesplin
91
Der große Unterschied zwischen dem Herunterfahren und Schließen eines Sockets besteht im Verhalten, wenn der Socket von anderen Prozessen gemeinsam genutzt wird. Ein shutdown () wirkt sich auf alle Kopien des Sockets aus, während close () nur den Dateideskriptor in einem Prozess betrifft.
Zan Lynx
5
Ein Grund, warum Sie möglicherweise shutdownbeide Richtungen verwenden möchten, ist jedoch nicht, closewenn Sie mit FILEauf die Buchse verwiesen haben fdopen. Wenn Sie closeden Socket haben, könnte einer neu geöffneten Datei derselbe fd zugewiesen werden, und die anschließende Verwendung des FILEwird den falschen Ort lesen / schreiben, was sehr schlecht sein könnte. Wenn Sie nur shutdown, wird die spätere Verwendung von FILEnur Fehler geben, bis fcloseaufgerufen wird.
R .. GitHub STOP HELPING ICE
25
Der Kommentar zu TIME_WAIT ist falsch. Das gilt für Ports, nicht für Sockets. Sie können Sockets nicht wiederverwenden.
Marquis von Lorne
48
-1. Sowohl dieser Beitrag als auch der Link lassen einen wichtigen konzeptionellen Grund aus, den Sie verwenden möchten shutdown: EOF an den Peer zu signalisieren und dennoch ausstehende Daten empfangen zu können, die der Peer gesendet hat.
Joey Adams
144

Keine der vorhandenen Antworten sagt den Leuten, wie shutdownund closeauf TCP-Protokollebene funktioniert, daher lohnt es sich, dies hinzuzufügen.

Eine Standard-TCP-Verbindung wird durch 4-Wege-Finalisierung beendet:

  1. Sobald ein Teilnehmer keine Daten mehr zu senden hat, sendet er ein FIN-Paket an den anderen
  2. Die andere Partei gibt eine Bestätigung für die FIN zurück.
  3. Wenn der andere Teilnehmer auch die Datenübertragung beendet hat, sendet er ein weiteres FIN-Paket
  4. Der ursprüngliche Teilnehmer gibt eine Bestätigung zurück und schließt die Übertragung ab.

Es gibt jedoch eine andere "emergente" Möglichkeit, eine TCP-Verbindung zu schließen:

  1. Ein Teilnehmer sendet ein RST-Paket und bricht die Verbindung ab
  2. Die andere Seite erhält eine RST und bricht dann ebenfalls die Verbindung ab

In meinem Test mit Wireshark mit den Standard-Socket-Optionen wird shutdownein FIN-Paket an das andere Ende gesendet, aber es ist alles, was es tut. Bis die andere Partei Ihnen das FIN-Paket sendet, können Sie weiterhin Daten empfangen. Sobald dies passiert ist, Receiveerhalten Sie ein Ergebnis der Größe 0. Wenn Sie also der erste sind, der "Senden" herunterfährt, sollten Sie den Socket schließen, sobald Sie den Datenempfang abgeschlossen haben.

Wenn Sie dagegen anrufen, closewährend die Verbindung noch aktiv ist (die andere Seite ist noch aktiv und Sie haben möglicherweise auch nicht gesendete Daten im Systempuffer), wird ein RST-Paket an die andere Seite gesendet. Das ist gut für Fehler. Wenn Sie beispielsweise der Meinung sind, dass die andere Partei falsche Daten angegeben hat oder sich geweigert hat, Daten bereitzustellen (DOS-Angriff?), Können Sie den Socket sofort schließen.

Meine Meinung zu Regeln wäre:

  1. Überlegen Sie shutdownvorher, closewann immer dies möglich ist
  2. Wenn Sie den Empfang abgeschlossen haben (Daten der Größe 0 empfangen), bevor Sie sich zum Herunterfahren entschieden haben, schließen Sie die Verbindung nach dem letzten Senden (falls vorhanden).
  3. Wenn Sie die Verbindung normal schließen möchten, beenden Sie die Verbindung (mit SHUT_WR, und wenn Sie nach diesem Zeitpunkt keine Daten mehr empfangen möchten, auch mit SHUT_RD), und warten Sie, bis Sie Daten der Größe 0 erhalten, und schließen Sie dann die Verbindung Steckdose.
  4. In jedem Fall, wenn ein anderer Fehler aufgetreten ist (z. B. Timeout), schließen Sie einfach den Socket.

Ideale Implementierungen für SHUT_RD und SHUT_WR

Folgendes wurde nicht getestet, Vertrauen auf eigenes Risiko. Ich glaube jedoch, dass dies eine vernünftige und praktische Art ist, Dinge zu tun.

Wenn der TCP-Stack nur mit SHUT_RD heruntergefahren wird, markiert er diese Verbindung als keine weiteren erwarteten Daten. Alle ausstehenden und nachfolgenden readAnforderungen (unabhängig davon, in welchem ​​Thread sie sich befinden) werden dann mit einem Ergebnis von Null zurückgegeben. Die Verbindung ist jedoch weiterhin aktiv und verwendbar - Sie können beispielsweise weiterhin OOB-Daten empfangen. Außerdem löscht das Betriebssystem alle Daten, die es für diese Verbindung empfängt. Aber das ist alles, es werden keine Pakete an die andere Seite gesendet.

Wenn der TCP-Stack nur mit SHUT_WR heruntergefahren wird, markiert er diese Verbindung, da keine Daten mehr gesendet werden können. Alle ausstehenden Schreibanforderungen werden beendet, nachfolgende Schreibanforderungen schlagen jedoch fehl. Außerdem wird ein FIN-Paket an eine andere Seite gesendet, um sie darüber zu informieren, dass wir keine weiteren Daten zum Senden haben.

Erdmotor
quelle
2
"Wenn Sie schließen, während die Verbindung noch besteht" ist tautologisch und es wird kein RST gesendet. (1) ist nicht notwendig. In (4) sind Zeitüberschreitungen für die Verbindung nicht unbedingt schwerwiegend und zeigen nicht immer an, dass Sie sie schließen können.
Marquis von Lorne
4
@EJP Nein, es ist nicht tautologisch. Sie können shutdown()die Verbindung und dann ist es nicht mehr lebendig. Sie haben noch den Dateideskriptor. Sie können immer noch recv()aus dem Empfangspuffer. Und Sie müssen immer noch aufrufen close(), um den Dateideskriptor zu entsorgen.
Pavel Šimerda
Dies ist die beste Antwort in Bezug auf das Drahtformat. Ich würde mich für weitere Details zum Herunterfahren mit SHUT_RD interessieren. Es gibt keine TCP-Signalisierung, um nicht mehr Daten zu erwarten, oder? Gibt es nicht nur FIN für die Signalisierung, dass keine weiteren Daten gesendet werden?
Pavel Šimerda
3
@ PavelŠimerda Ja TCP signalisiert nicht, dass keine weiteren Daten erwartet werden. Dies sollte in den High-Level-Protokollen berücksichtigt werden. Meiner Meinung nach ist dies im Allgemeinen nicht notwendig. Sie können Ihre Tür schließen, aber Sie können nicht verhindern, dass Leute Geschenke vor die Tür stellen. Dies ist IHRE Entscheidung, nicht deine.
Earth Engine
1
@EJP Hast du irgendwelche tatsächlichen Argumente? Sonst bin ich nicht interessiert.
Pavel Šimerda
35

Es gibt einige Einschränkungen close(), die vermieden werden können, wenn man shutdown()stattdessen verwendet.

close()beendet beide Richtungen bei einer TCP-Verbindung. Manchmal möchten Sie dem anderen Endpunkt mitteilen, dass Sie mit dem Senden der Daten fertig sind, aber dennoch Daten empfangen möchten.

close()Verringert die Referenzanzahl der Deskriptoren (wird im Dateitabelleneintrag beibehalten und zählt die Anzahl der aktuell geöffneten Deskriptoren, die sich auf eine Datei / einen Socket beziehen) und schließt den Socket / die Datei nicht, wenn der Deskriptor nicht 0 ist. Die Bereinigung erfolgt erst, nachdem der Referenzzähler auf 0 gefallen ist. Mit shutdown()kann eine normale TCP-Schließsequenz initiiert werden, wobei der Referenzzähler ignoriert wird.

Die Parameter sind wie folgt:

int shutdown(int s, int how); // s is socket descriptor

int how kann sein:

SHUT_RDoder 0 Weitere Empfänge sind nicht zulässig

SHUT_WRoder 1 Weitere Sendungen sind nicht zulässig

SHUT_RDWRoder 2 Weitere Sendungen und Empfänge sind nicht zulässig

Mailand
quelle
8
Die beiden Funktionen dienen völlig unterschiedlichen Zwecken. Die Tatsache, dass das endgültige Schließen eines Sockets eine Abschaltsequenz initiiert, wenn noch keine initiiert wurde, ändert nichts an der Tatsache, dass das Schließen zum Bereinigen der Socket-Datenstrukturen und das Herunterfahren zum Initiieren einer Abschaltsequenz auf TCP-Ebene dient.
Len Holgate
25
Sie können stattdessen nicht "shutdown" verwenden. Sie können es auch verwenden. Sie müssen die Steckdose jedoch einige Zeit schließen.
Marquis von Lorne
15

Dies mag plattformspezifisch sein, ich bezweifle es irgendwie, aber die beste Erklärung, die ich gesehen habe, ist hier auf dieser MSDN-Seite, wo sie das Herunterfahren, Verweiloptionen, Socket-Schließen und allgemeine Verbindungsbeendigungssequenzen erklären.

Zusammenfassend können Sie mit shutdown eine Shutdown-Sequenz auf TCP-Ebene senden und mit close die Ressourcen freigeben, die von den Socket-Datenstrukturen in Ihrem Prozess verwendet werden. Wenn Sie zum Zeitpunkt des Aufrufs von close noch keine explizite Abschaltsequenz ausgegeben haben, wird eine für Sie initiiert.

Len Holgate
quelle
9

Ich hatte auch Erfolg unter Linux mit shutdown()einem Pthread, um einen anderen derzeit blockierten Pthread zu zwingen connect(), vorzeitig abzubrechen.

Unter anderen Betriebssystemen (zumindest OSX) fand ich, dass das Anrufen close()ausreichte, um connect()fehlzuschlagen.

Toby
quelle
7

"shutdown () schließt den Dateideskriptor nicht wirklich - es ändert nur seine Verwendbarkeit. Um einen Socket-Deskriptor freizugeben, müssen Sie close () verwenden." 1


quelle
3

Schließen

Wenn Sie einen Socket nicht mehr verwenden, können Sie den Dateideskriptor einfach mit close schließen. Wenn immer noch Daten auf die Übertragung über die Verbindung warten, versucht normalerweise close, diese Übertragung abzuschließen. Sie können dieses Verhalten mithilfe der Socket-Option SO_LINGER steuern, um eine Zeitüberschreitungsperiode anzugeben. Siehe Socket-Optionen.

Herunterfahren

Sie können auch nur den Empfang oder die Übertragung einer Verbindung beenden, indem Sie shutdown aufrufen.

Die Abschaltfunktion unterbricht die Verbindung der Steckdose. Das Argument, wie angegeben wird, welche Aktion ausgeführt werden soll: 0 Stoppen Sie den Empfang von Daten für diesen Socket. Wenn weitere Daten eingehen, lehnen Sie diese ab. 1 Versuchen Sie nicht mehr, Daten von diesem Socket zu übertragen. Verwerfen Sie alle Daten, die darauf warten, gesendet zu werden. Suchen Sie nicht mehr nach einer Bestätigung der bereits gesendeten Daten. Übertragen Sie es nicht erneut, wenn es verloren geht. 2 Stoppen Sie sowohl den Empfang als auch die Übertragung.

Der Rückgabewert ist 0 bei Erfolg und -1 bei Fehler.

Neha Agrawal
quelle
2

in meinem Test.

close sendet ein fin-Paket und zerstört fd sofort, wenn der Socket nicht mit anderen Prozessen geteilt wird

shutdown SHUT_RD , der Prozess kann weiterhin Daten vom Socket abrufen, gibt jedoch recv0 zurück, wenn der TCP-Puffer leer ist. Nachdem der Peer weitere Daten gesendet hat, recvwerden die Daten erneut zurückgegeben.

shutdown SHUT_WR sendet ein Fin-Paket, um anzuzeigen, dass weitere Sendungen nicht zulässig sind. Der Peer kann Daten empfangen, aber er empfängt 0, wenn sein TCP-Puffer leer ist

shutdown SHUT_RDWR (entspricht sowohl SHUT_RD als auch SHUT_WR ) sendet das erste Paket, wenn der Peer mehr Daten sendet.

simpx
quelle
Ich habe es gerade versucht und close()RST unter Linux anstelle von FIN gesendet.
Pavel Šimerda
Wollten Sie auch wirklich sagen, dass das Programm nach dem Herunterfahren der Leseseite mehr Daten erhält?
Pavel Šimerda
@ PavelŠimerda Ich denke, es kann vom TCP-Status abhängen, RST oder FIN zu senden? ich bin mir nicht sicher.
Simpx
1. 'Nachdem der Peer weitere Daten gesendet hat, recv()werden die Daten erneut zurückgegeben' ist nicht korrekt. 2. Das Verhalten, wenn der Peer danach weitere Daten sendet, SHUT_RDist plattformabhängig.
Marquis von Lorne
@EJP Ich habe es selbst versucht, sorry, ich habe vergessen, auf welcher Plattform ich diesen Test mache, Centos oder Mac. und das habe ich bekommen
simpx