Als Antwort auf eine aktuelle Frage frage ich mich, warum es in Java unmöglich ist, ohne den Versuch, auf einem TCP-Socket zu lesen / schreiben, festzustellen, dass der Socket vom Peer ordnungsgemäß geschlossen wurde. Dies scheint unabhängig davon der Fall zu sein, ob man das Pre-NIO Socket
oder das NIO verwendet SocketChannel
.
Wenn ein Peer eine TCP-Verbindung ordnungsgemäß schließt, wissen die TCP-Stapel auf beiden Seiten der Verbindung Bescheid. Die Serverseite (die das Herunterfahren initiiert) befindet sich im Status FIN_WAIT2
, während die Clientseite (die nicht explizit auf das Herunterfahren reagiert) im Status endet CLOSE_WAIT
. Warum gibt es nicht ein Verfahren in Socket
oder SocketChannel
dass der TCP - Stack abfragen können , um zu sehen , ob die zugrunde liegende TCP - Verbindung beendet wurde? Ist es so, dass der TCP-Stack solche Statusinformationen nicht bereitstellt? Oder ist es eine Entwurfsentscheidung, einen kostspieligen Aufruf des Kernels zu vermeiden?
Mit Hilfe der Benutzer, die bereits einige Antworten auf diese Frage gepostet haben, sehe ich, woher das Problem kommen könnte. Die Seite, die die Verbindung nicht explizit schließt, befindet sich im TCP-Status. Dies CLOSE_WAIT
bedeutet, dass die Verbindung gerade heruntergefahren wird und darauf wartet, dass die Seite ihren eigenen CLOSE
Vorgang ausführt. Ich , dass es fair genug nehme an, dass isConnected
Erträge true
und isClosed
Renditen false
, aber warum nicht , wie es etwas gibt isClosing
?
Nachfolgend sind die Testklassen aufgeführt, die Pre-NIO-Sockets verwenden. Mit NIO werden jedoch identische Ergebnisse erzielt.
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
Wenn der Testclient eine Verbindung zum Testserver herstellt, bleibt die Ausgabe unverändert, auch nachdem der Server das Herunterfahren der Verbindung initiiert hat:
connected: true, closed: false
connected: true, closed: false
...
quelle
Socket.close
ist kein anmutiger Abschluss.Antworten:
Ich habe Sockets häufig verwendet, hauptsächlich mit Selektoren, und obwohl ich kein Network OSI-Experte bin,
shutdownOutput()
sendet der Aufruf eines Sockets nach meinem Verständnis tatsächlich etwas im Netzwerk (FIN), das meinen Selector auf der anderen Seite aufweckt (dasselbe Verhalten in C. Sprache). Hier haben Sie Erkennung : Sie erkennen tatsächlich einen Lesevorgang, der fehlschlägt, wenn Sie es versuchen.In dem von Ihnen angegebenen Code werden durch Schließen des Sockets sowohl Eingabe- als auch Ausgabestreams heruntergefahren, ohne dass die verfügbaren Daten gelesen werden können, wodurch sie verloren gehen. Die Java-
Socket.close()
Methode führt eine "anmutige" Trennung durch (entgegengesetzt zu dem, was ich ursprünglich gedacht habe), indem die im Ausgabestream verbleibenden Daten gesendet werden, gefolgt von einem FIN , um das Schließen zu signalisieren. Die FIN wird von der anderen Seite bestätigt, wie jedes reguläre Paket 1 .Wenn Sie warten müssen, bis die andere Seite ihren Sockel schließt, müssen Sie auf ihren FIN warten. Und zu erreichen , dass, Sie müssen erkennen
Socket.getInputStream().read() < 0
, was bedeutet , sollten Sie nicht nahe Ihrer Steckdose, wie es wäre seine schließenInputStream
.Nach dem, was ich in C und jetzt in Java getan habe, sollte das Erreichen eines solchen synchronisierten Abschlusses folgendermaßen erfolgen:
read()
die Fernbedienung erkennen könnenclose()
InputStream
bis wir die Antwort-FIN vom anderen Ende erhalten (da sie die FIN erkennt, durchläuft sie denselben ordnungsgemäßen Verbindungsvorgang). Dies ist bei einigen Betriebssystemen wichtig, da sie den Socket nicht schließen, solange einer seiner Puffer noch Daten enthält. Sie werden als "Ghost" -Socket bezeichnet und verbrauchen Deskriptornummern im Betriebssystem (das ist bei modernen Betriebssystemen möglicherweise kein Problem mehr).Socket.close()
oder schließenInputStream
oderOutputStream
)Wie im folgenden Java-Snippet gezeigt:
Natürlich beide Seiten müssen die Schließung die gleiche Weise verwenden oder das Sendeteil immer genügend Daten aussenden könnte das halte
while
Schleife beschäftigt (zB wenn das Sendeteil nur Daten sendet und nie Verbindungsabbruch Lese zu erkennen. Welche ungeschickt ist, aber Sie haben möglicherweise keine Kontrolle darüber).Wie @WarrenDew in seinem Kommentar hervorhob, führt das Verwerfen der Daten im Programm (Anwendungsschicht) zu einer nicht ordnungsgemäßen Trennung auf Anwendungsebene: obwohl alle Daten auf TCP-Ebene empfangen wurden (die
while
Schleife) , werden sie verworfen.1 : Aus " Fundamental Networking in Java ": siehe Abb. 3.3 S.45 und der gesamte §3.7, S. 43-48
quelle
socket.close()
ist "brutal", da es diese Client / Server-Signalisierung nicht berücksichtigt. Der Server wird nur dann über eine Client-Trennung benachrichtigt, wenn sein eigener Socket-Ausgabepuffer voll ist (da keine Daten von der anderen Seite bestätigt werden, die geschlossen wurde).read()
gibt am Ende des Streams -1 zurück und tut dies auch weiterhin, wie oft Sie es aufrufen. ACread()
oderrecv()
gibt am Ende des Streams Null zurück und tut dies auch weiterhin, wie oft Sie es aufrufen.Ich denke, das ist eher eine Frage der Socket-Programmierung. Java folgt nur der Tradition der Socket-Programmierung.
Aus Wikipedia :
Sobald der Handshake abgeschlossen ist, unterscheidet TCP nicht mehr zwischen zwei Endpunkten (Client und Server). Die Begriffe "Client" und "Server" dienen hauptsächlich der Vereinfachung. Der "Server" könnte also Daten senden, und der "Client" könnte einige andere Daten gleichzeitig aneinander senden.
Der Begriff "Schließen" ist ebenfalls irreführend. Es gibt nur eine FIN-Erklärung, was bedeutet: "Ich werde Ihnen keine weiteren Sachen schicken." Dies bedeutet jedoch nicht, dass sich keine Pakete im Flug befinden oder der andere nichts mehr zu sagen hat. Wenn Sie Schneckenpost als Datenverbindungsschicht implementieren oder wenn Ihr Paket unterschiedliche Routen zurückgelegt hat, empfängt der Empfänger möglicherweise Pakete in falscher Reihenfolge. TCP weiß, wie Sie dies für Sie beheben können.
Außerdem haben Sie als Programm möglicherweise keine Zeit, weiter zu überprüfen, was sich im Puffer befindet. So können Sie nach Belieben überprüfen, was sich im Puffer befindet. Alles in allem ist die aktuelle Socket-Implementierung nicht so schlecht. Wenn es tatsächlich isPeerClosed () gab, ist dies ein zusätzlicher Aufruf, den Sie jedes Mal tätigen müssen, wenn Sie read aufrufen möchten.
quelle
Die zugrunde liegende Sockets-API verfügt nicht über eine solche Benachrichtigung.
Der sendende TCP-Stapel sendet das FIN-Bit ohnehin erst beim letzten Paket, sodass möglicherweise viele Daten gepuffert werden, wenn die sendende Anwendung ihren Socket logisch geschlossen hat, bevor diese Daten überhaupt gesendet werden. Ebenso können Daten, die gepuffert sind, weil das Netzwerk schneller als die empfangende Anwendung ist (ich weiß nicht, vielleicht leiten Sie sie über eine langsamere Verbindung weiter), für den Empfänger von Bedeutung sein und Sie möchten nicht, dass die empfangende Anwendung sie verwirft nur weil das FIN-Bit vom Stapel empfangen wurde.
quelle
Da bisher keine der Antworten die Frage vollständig beantwortet, fasse ich mein derzeitiges Verständnis des Problems zusammen.
Wenn eine TCP-Verbindung hergestellt wird und ein Peer anruft
close()
odershutdownOutput()
an seinem Socket, wechselt der Socket auf der anderen Seite der Verbindung in denCLOSE_WAIT
Status. Grundsätzlich kann aus dem TCP-Stack ermittelt werden, ob sich ein Socket imCLOSE_WAIT
Status befindet, ohne ihn aufzurufenread/recv
(z. B.getsockopt()
unter Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ), aber das ist nicht der Fall tragbar.Javas
Socket
Klasse scheint so konzipiert zu sein, dass sie eine Abstraktion bietet, die mit einem BSD-TCP-Socket vergleichbar ist, wahrscheinlich weil dies die Abstraktionsebene ist, an die Benutzer beim Programmieren von TCP / IP-Anwendungen gewöhnt sind. BSD-Sockets sind eine Verallgemeinerung, die andere Sockets als nur INET-Sockets (z. B. TCP) unterstützt. Sie bieten daher keine tragbare Möglichkeit, den TCP-Status eines Sockets zu ermitteln.Es gibt keine Methode wie diese,
isCloseWait()
da Benutzer, die TCP-Anwendungen auf der von BSD-Sockets angebotenen Abstraktionsebene programmieren, nicht erwarten, dass Java zusätzliche Methoden bereitstellt.quelle
isCloseWait()
da sie nicht auf allen Plattformen unterstützt wird.Mit der Methode java.net.Socket.sendUrgentData (int) können Sie feststellen, ob die Remote-Seite einer (TCP) Socket-Verbindung geschlossen wurde, und die IOException abfangen, die ausgelöst wird, wenn die Remote-Seite nicht verfügbar ist. Dies wurde zwischen Java-Java und Java-C getestet.
Dies vermeidet das Problem des Entwurfs des Kommunikationsprotokolls zur Verwendung eines Ping-Mechanismus. Durch Deaktivieren von OOBInline an einem Socket (setOOBInline (false) werden alle empfangenen OOB-Daten stillschweigend verworfen, OOB-Daten können jedoch weiterhin gesendet werden. Wenn die Remote-Seite geschlossen wird, wird ein Verbindungsrücksetzen versucht, schlägt fehl und es wird eine IOException ausgelöst .
Wenn Sie tatsächlich OOB-Daten in Ihrem Protokoll verwenden, kann Ihr Kilometerstand variieren.
quelle
Der Java IO-Stack sendet definitiv FIN, wenn er bei einem plötzlichen Herunterfahren zerstört wird. Es macht einfach keinen Sinn, dass Sie dies nicht erkennen können. Die meisten Clients senden die FIN nur, wenn sie die Verbindung beenden.
... ein weiterer Grund, warum ich die NIO Java-Klassen wirklich hasse. Es scheint, als wäre alles ein bisschen halbherzig.
quelle
Es ist ein interessantes Thema. Ich habe gerade den Java-Code durchgesehen, um ihn zu überprüfen. Nach meiner Feststellung gibt es zwei unterschiedliche Probleme: Das erste ist der TCP-RFC selbst, der es ermöglicht, dass ferngesteuerte Sockets Daten im Halbduplex übertragen, sodass ein entfernt geschlossener Socket immer noch halb offen ist. Gemäß RFC schließt RST die Verbindung nicht. Sie müssen einen expliziten ABORT-Befehl senden. Java ermöglicht also das Senden von Daten über einen halb geschlossenen Socket
(Es gibt zwei Methoden zum Lesen des Schließstatus an beiden Endpunkten.)
Das andere Problem ist, dass die Implementierung angibt, dass dieses Verhalten optional ist. Da Java bestrebt ist, portabel zu sein, haben sie die beste gemeinsame Funktion implementiert. Die Pflege einer Karte von (Betriebssystem, Implementierung von Halbduplex) wäre wohl ein Problem gewesen.
quelle
Dies ist ein Fehler der OO-Socket-Klassen von Java (und allen anderen, die ich mir angesehen habe) - kein Zugriff auf den ausgewählten Systemaufruf.
Richtige Antwort in C:
quelle
getsocketop(... SOL_SOCKET, SO_ERROR, ...)
.Hier ist eine lahme Problemumgehung. Verwenden Sie SSL;) und SSL führt beim Herunterfahren einen engen Handshake durch, sodass Sie benachrichtigt werden, dass der Socket geschlossen wird (die meisten Implementierungen scheinen einen eigentlichen Handshake-Teardown durchzuführen).
quelle
Der Grund für dieses Verhalten (das nicht Java-spezifisch ist) ist die Tatsache, dass Sie keine Statusinformationen vom TCP-Stapel erhalten. Schließlich ist ein Socket nur ein weiteres Dateihandle, und Sie können nicht herausfinden, ob tatsächlich Daten zum Lesen vorhanden sind, ohne es tatsächlich zu versuchen (
select(2)
dies hilft nicht, es signalisiert nur, dass Sie es versuchen können, ohne es zu blockieren).Weitere Informationen finden Sie in den häufig gestellten Fragen zu Unix-Sockets .
quelle
select()
Zeigt an, ob Daten oder EOS zum Lesen verfügbar sind, ohne zu blockieren. "Signale, die Sie versuchen können, ohne zu blockieren" ist bedeutungslos. Wenn Sie sich im nicht blockierenden Modus befinden, können Sie es jederzeit ohne Blockierung versuchen.select()
wird von Daten im Socket-Empfangspuffer oder einer ausstehenden FIN oder einem Leerzeichen im Socket-Sendepuffer gesteuert.getsockopt(SO_ERROR)
? In der Tat wird sogargetpeername
angezeigt, ob die Steckdose noch angeschlossen ist.Nur Schreibvorgänge erfordern den Austausch von Paketen, wodurch der Verbindungsverlust festgestellt werden kann. Eine übliche Problemumgehung ist die Verwendung der Option KEEP ALIVE.
quelle
Wenn es um halboffene Java-Sockets geht, sollten Sie sich isInputShutdown () und isOutputShutdown () ansehen .
quelle