Wann ist die TCP-Option SO_LINGER (0) erforderlich?

95

Ich glaube, ich verstehe die formale Bedeutung der Option. In einigen Legacy-Codes, die ich gerade verarbeite, wird die Option verwendet. Der Kunde beschwert sich über RST als Antwort auf FIN von seiner Seite bei einer Verbindung von seiner Seite.

Ich bin nicht sicher, ob ich es sicher entfernen kann, da ich nicht verstehe, wann es verwendet werden sollte.

Können Sie bitte ein Beispiel geben, wann die Option erforderlich wäre?

dimba
quelle
1
Sie sollten es entfernen. Es sollte nicht im Produktionscode verwendet werden. Das einzige Mal, dass ich es jemals gesehen habe, war das Ergebnis eines ungültigen Benchmarks.
Marquis von Lorne

Antworten:

81

Der typische Grund für das SO_LINGERFestlegen eines Zeitlimits von Null besteht darin, eine große Anzahl von Verbindungen im TIME_WAITStatus zu vermeiden und alle verfügbaren Ressourcen auf einem Server zu binden.

Wenn eine TCP-Verbindung sauber geschlossen wird, bleibt die Verbindung am Ende, das das Schließen initiiert hat ("aktives Schließen"), TIME_WAITeinige Minuten lang bestehen. Wenn es sich bei Ihrem Protokoll also um ein Protokoll handelt, bei dem der Server das Schließen der Verbindung initiiert und eine sehr große Anzahl kurzlebiger Verbindungen umfasst, ist dieses Problem möglicherweise anfällig.

Dies ist jedoch keine gute Idee - TIME_WAITexistiert aus einem bestimmten Grund (um sicherzustellen, dass Streupakete von alten Verbindungen neue Verbindungen nicht stören). Es ist besser, das Protokoll so zu gestalten, dass der Client den Verbindungsabschluss nach Möglichkeit initiiert.

caf
quelle
3
Ich bin vollkommen einverstanden. Ich habe eine Überwachungsanwendung gesehen, die viele initiierte (einige Tausend kurzlebige Verbindungen alle X Sekunden), und es war wahrscheinlich, dass sie größer skaliert wurde (tausend Verbindungen mehr). Ich weiß nicht warum, aber die Anwendung reagierte nicht. Jemand schlug SO_LINGER = true, TIME_WAIT = 0 vor, um Betriebssystemressourcen schnell freizugeben, und nach kurzer Untersuchung haben wir diese Lösung mit sehr guten Ergebnissen ausprobiert. Die TIME_WAIT ist für diese App kein Problem mehr.
Bartosz.r
24
Ich bin nicht einverstanden. Ein Protokoll auf Anwendungsebene, das über TCP liegt, sollte so konzipiert sein, dass der Client das Schließen der Verbindung immer initiiert. Auf diese Weise TIME_WAITsitzt der Wille beim Kunden und schadet ihm nicht. Denken Sie daran, wie es in der dritten Ausgabe von "UNIX Network Programming" (Stevens et al.) Auf Seite 203 heißt: "Der Status TIME_WAIT ist Ihr Freund und hilft uns. Anstatt zu versuchen, den Status zu vermeiden, sollten wir ihn verstehen (Abschnitt 2.7). . "
mgd
8
Was ist, wenn ein Client alle 30 Sekunden 4000 Verbindungen öffnen möchte (diese Überwachungsanwendung ist ein Client! Weil sie eine Verbindung initiiert)? Ja, wir können die Anwendung neu erstellen, einige lokale Agenten in die Infrastruktur einfügen und das Modell auf Push ändern. Aber wenn wir bereits eine solche Anwendung haben und sie wächst, können wir sie zum Funktionieren bringen, indem wir zwei verweilen. Sie ändern einen Parameter und haben plötzlich eine funktionierende Anwendung, ohne ein Budget für die Implementierung einer neuen Architektur zu investieren.
Bartosz.r
3
@ bartosz.r: Ich sage nur, dass die Verwendung von SO_LINGER mit Timeout 0 wirklich ein letzter Ausweg sein sollte. In der dritten Ausgabe von "UNIX Network Programming" (Stevens et al.) Auf Seite 203 heißt es erneut, dass Sie Datenkorruption riskieren. Lesen Sie RFC 1337, wo Sie sehen können, warum TIME_WAIT Ihr Freund ist.
mgd
7
@caf Nein, die klassische Lösung wäre ein Verbindungspool, wie er in jeder Hochleistungs-TCP-API zu sehen ist, z. B. HTTP 1.1.
Marquis von Lorne
187

Lesen Sie für meinen Vorschlag den letzten Abschnitt: „Wann wird SO_LINGER mit Timeout 0 verwendet?“ .

Bevor wir dazu kommen, ein kleiner Vortrag über:

  • Normale TCP-Beendigung
  • TIME_WAIT
  • FIN, ACKundRST

Normale TCP-Beendigung

Die normale TCP-Beendigungssequenz sieht folgendermaßen aus (vereinfacht):

Wir haben zwei Kollegen: A und B.

  1. Ein Anruf close()
    • A sendet FINan B.
    • A geht in den FIN_WAIT_1Zustand
  2. B erhält FIN
    • B sendet ACKan A.
    • B geht in den CLOSE_WAITZustand
  3. A erhält ACK
    • A geht in den FIN_WAIT_2Zustand
  4. B ruft an close()
    • B sendet FINan A.
    • B geht in den LAST_ACKZustand
  5. A erhält FIN
    • A sendet ACKan B.
    • A geht in den TIME_WAITZustand
  6. B erhält ACK
    • B geht in den CLOSEDZustand - dh wird aus den Socket-Tabellen entfernt

ZEIT WARTET

Der Peer, der die Kündigung initiiert - dh close()zuerst anruft -, landet also im TIME_WAITStatus.

Um zu verstehen, warum der TIME_WAITStaat unser Freund ist, lesen Sie bitte Abschnitt 2.7 in der dritten Ausgabe von "UNIX Network Programming" von Stevens et al. (Seite 43).

Es kann jedoch ein Problem mit vielen Sockets TIME_WAITauf einem Server sein, da möglicherweise verhindert wird, dass neue Verbindungen akzeptiert werden.

Um dieses Problem zu umgehen, habe ich viele Vorschläge gesehen, die Socket-Option SO_LINGER mit dem Timeout 0 vor dem Aufruf festzulegen close(). Dies ist jedoch eine schlechte Lösung, da dadurch die TCP-Verbindung mit einem Fehler beendet wird.

Entwerfen Sie stattdessen Ihr Anwendungsprotokoll so, dass die Verbindungsbeendigung immer von der Clientseite aus initiiert wird. Wenn der Client immer weiß, wann er alle verbleibenden Daten gelesen hat, kann er die Beendigungssequenz initiieren. Ein Browser weiß beispielsweise aus dem Content-LengthHTTP-Header, wann er alle Daten gelesen hat, und kann das Schließen einleiten. (Ich weiß, dass es in HTTP 1.1 für eine mögliche Wiederverwendung eine Weile geöffnet bleibt und dann geschlossen wird.)

Wenn der Server die Verbindung schließen muss, entwerfen Sie das Anwendungsprotokoll so, dass der Server den Client zum Aufrufen auffordert close().

Wann wird SO_LINGER mit Timeout 0 verwendet?

Gemäß der dritten Ausgabe von "UNIX Network Programming", Seite 202-203, führt die Einstellung SO_LINGERmit Timeout 0 vor dem Aufruf close()dazu, dass die normale Beendigungssequenz nicht initiiert wird.

Stattdessen close()sendet der Peer, der diese Option einstellt und aufruft , ein RST(Verbindungs-Reset), das auf einen Fehlerzustand hinweist, und so wird es am anderen Ende wahrgenommen. In der Regel werden Fehler wie "Verbindung durch Peer zurückgesetzt" angezeigt.

Daher ist es in der normalen Situation eine wirklich schlechte Idee, SO_LINGERvor dem Anruf das Zeitlimit 0 festzulegen close()- von nun an angerufen eine Serveranwendung abortives Schließen bezeichnet .

Bestimmte Situationen rechtfertigen dies jedoch trotzdem:

  • Wenn sich ein Client Ihrer Serveranwendung schlecht verhält (Zeitüberschreitung, Rückgabe ungültiger Daten usw.), ist ein fehlgeschlagener Abschluss sinnvoll, um zu vermeiden, dass er in der Server-Anwendung stecken bleibt CLOSE_WAIToder in dieser endetTIME_WAIT Status Status .
  • Wenn Sie Ihre Serveranwendung neu starten müssen, die derzeit über Tausende von Clientverbindungen verfügt, können Sie diese Socket-Option festlegen, um Tausende von Server-Sockets zu vermeiden TIME_WAIT(wenn Sie close()vom Server aus anrufen ), da dies möglicherweise verhindert, dass der Server verfügbare Ports für neue Clientverbindungen erhält nach dem Neustart.
  • Auf Seite 202 des oben genannten Buches heißt es ausdrücklich: "Es gibt bestimmte Umstände, die die Verwendung dieser Funktion zum Senden eines fehlgeschlagenen Abschlusses rechtfertigen. Ein Beispiel ist ein RS-232-Terminalserver, der beim CLOSE_WAITVersuch, Daten an ein feststeckendes Terminal zu liefern , möglicherweise für immer hängen bleibt Port, würde aber den stecken gebliebenen Port ordnungsgemäß zurücksetzen, wenn er RSTdie ausstehenden Daten verwerfen müsste . "

Ich würde diesen langen Artikel empfehlen, der meiner Meinung nach eine sehr gute Antwort auf Ihre Frage gibt.

mgd
quelle
6
TIME_WAITist nur dann ein Freund, wenn es keine Probleme verursacht: stackoverflow.com/questions/1803566/…
Pacerier
2
Was ist, wenn Sie einen Webserver schreiben? Wie können Sie "den Kunden anweisen, einen Abschluss einzuleiten"?
Shaun Neal
2
@ShaunNeal das tust du offensichtlich nicht. Ein gut geschriebener Client / Browser leitet jedoch das Schließen ein. Wenn sich der Client nicht gut benimmt, haben wir zum Glück das Attentat TIME_WAIT, um sicherzustellen, dass uns nicht die Socket-Deskriptoren und kurzlebigen Ports ausgehen.
mgd
16

Wenn die Verweilzeit aktiviert ist, das Zeitlimit jedoch Null ist, wartet der TCP-Stapel nicht auf das Senden ausstehender Daten, bevor die Verbindung geschlossen wird. Aufgrund dessen können Daten verloren gehen. Wenn Sie jedoch das Verweilen auf diese Weise einstellen, akzeptieren Sie dies und fordern, dass die Verbindung sofort zurückgesetzt wird, anstatt ordnungsgemäß geschlossen zu werden. Dies führt dazu, dass eine RST anstelle der üblichen FIN gesendet wird.

Vielen Dank an EJP für seinen Kommentar, siehe hier für Details.

Len Holgate
quelle
1
Ich verstehe das. Ich bitte um ein "realistisches" Beispiel, wenn wir einen Hard-Reset verwenden möchten.
Dimba
5
Wann immer Sie eine Verbindung abbrechen möchten; Wenn Ihr Protokoll die Validierung nicht besteht und ein Client plötzlich über Sie spricht, wird die Verbindung mit einem RST usw. abgebrochen.
Len Holgate
5
Sie verwechseln eine Null-Verweilzeit mit einer Verweilzeit. Verweilen bedeutet, dass close () nicht blockiert. Wenn Sie mit einem positiven Timeout fortfahren, wird close () bis zum Timeout blockiert. Das Verweilen mit einem Timeout von Null führt zu RST, und darum geht es bei der Frage.
Marquis von Lorne
2
Ja, du hast recht. Ich werde die Antwort anpassen, um meine Terminologie zu korrigieren.
Len Holgate
6

Ob Sie die Verweildauer in Ihrem Code sicher entfernen können oder nicht, hängt von der Art Ihrer Anwendung ab: Ist es ein „Client“ (TCP-Verbindungen öffnen und zuerst aktiv schließen) oder ist es ein „Server“ (Abhören eines offenen TCP und Schließen, nachdem die andere Seite das Schließen eingeleitet hat)?

Wenn Ihre Anwendung den Geschmack eines „Clients“ hat (zuerst schließen) UND Sie eine große Anzahl von Verbindungen zu verschiedenen Servern initiieren und schließen (z. B. wenn Ihre App eine Überwachungs-App ist, die die Erreichbarkeit einer großen Anzahl verschiedener Server überwacht), Ihre App hat das Problem, dass alle Ihre Client-Verbindungen im Status TIME_WAIT stecken bleiben. Dann würde ich empfehlen, das Zeitlimit auf einen kleineren Wert als den Standardwert zu verkürzen, um das System ordnungsgemäß herunterzufahren, aber die Ressourcen für Clientverbindungen früher freizugeben. Ich würde das Timeout nicht auf 0 setzen, da 0 mit FIN nicht ordnungsgemäß heruntergefahren, sondern mit RST abgebrochen wird.

Wenn Ihre Anwendung das Flair eines „Clients“ hat und eine große Anzahl kleiner Dateien vom selben Server abrufen muss, sollten Sie keine neue TCP-Verbindung pro Datei initiieren und in TIME_WAIT eine große Anzahl von Client-Verbindungen erhalten, sondern Lassen Sie die Verbindung offen und rufen Sie alle Daten über dieselbe Verbindung ab. Die verweilende Option kann und sollte entfernt werden.

Wenn Ihre Anwendung ein „Server“ ist (schließen als Reaktion auf das Schließen des Peers an zweiter Stelle), wird Ihre Verbindung bei close () ordnungsgemäß beendet und Ressourcen werden freigegeben, wenn Sie nicht in den Status TIME_WAIT wechseln. Verweilzeit sollte nicht verwendet werden. Wenn Ihre Server-App jedoch über einen Überwachungsprozess verfügt, der erkennt, dass inaktive offene Verbindungen für längere Zeit im Leerlauf sind („lang“ ist zu definieren), können Sie diese inaktive Verbindung von Ihrer Seite aus herunterfahren - sehen Sie dies als eine Art Fehlerbehandlung an - mit einem fehlgeschlagenen Herunterfahren. Dies erfolgt durch Setzen des Zeitlimits für das Verweilen auf 0. close () sendet dann eine RST an den Client und teilt ihm mit, dass Sie wütend sind :-)

Grandswiss
quelle
1

Auf Servern möchten Sie möglicherweise senden, RSTanstatt FINClients mit schlechtem Verhalten zu trennen. Dies überspringt, FIN-WAITgefolgt von TIME-WAITSocket-Status auf dem Server, wodurch verhindert wird, dass Serverressourcen erschöpft werden, und schützt somit vor dieser Art von Denial-of-Service-Angriff.

Maxim Egorushkin
quelle
0

Ich mag Maxim's Beobachtung, dass DOS-Angriffe Serverressourcen erschöpfen können. Es passiert auch ohne einen tatsächlich böswilligen Gegner.

Einige Server müssen sich mit dem "unbeabsichtigten DOS-Angriff" befassen, der auftritt, wenn die Client-App einen Fehler mit einem Verbindungsleck aufweist, bei dem sie für jeden neuen Befehl, den sie an Ihren Server senden, eine neue Verbindung herstellen. Und dann vielleicht ihre Verbindungen schließen, wenn sie auf GC-Druck stoßen, oder vielleicht die Verbindungen irgendwann auslaufen.

Ein anderes Szenario ist das Szenario "Alle Clients haben dieselbe TCP-Adresse". Dann sind Clientverbindungen nur durch Portnummern unterscheidbar (wenn sie eine Verbindung zu einem einzelnen Server herstellen). Und wenn Clients aus irgendeinem Grund schnell mit dem Öffnen / Schließen von Verbindungen beginnen, können sie den Tupelbereich (Client-Adresse + Port, Server-IP + Port) erschöpfen.

Daher denke ich, dass Server am besten empfohlen werden, zur Linger-Zero-Strategie zu wechseln, wenn sie eine hohe Anzahl von Sockets im Status TIME_WAIT sehen - obwohl dies das Clientverhalten nicht behebt, kann es die Auswirkungen verringern.

Tim Lovell-Smith
quelle
0

Der Listen-Socket auf einem Server kann mit der Zeit 0 verweilen, um sofort auf die Bindung an den Socket zuzugreifen und alle Clients zurückzusetzen, deren Verbindungen noch nicht hergestellt sind. TIME_WAIT ist etwas, das nur interessant ist, wenn Sie ein Netzwerk mit mehreren Pfaden haben und möglicherweise falsch geordnete Pakete erhalten oder sich auf andere Weise mit ungerader Netzwerkpaketbestellung / Ankunftszeit befassen.

Gregg Wunderbar
quelle