Java-Socket-API: Wie kann festgestellt werden, ob eine Verbindung geschlossen wurde?

92

Ich habe einige Probleme mit der Java-Socket-API. Ich versuche, die Anzahl der Spieler anzuzeigen, die derzeit mit meinem Spiel verbunden sind. Es ist leicht festzustellen, wann ein Spieler eine Verbindung hergestellt hat. Es scheint jedoch unnötig schwierig zu sein, festzustellen, wann ein Spieler mithilfe der Socket-API die Verbindung getrennt hat.

Das Anrufen isConnected()eines Sockets, der aus der Ferne getrennt wurde, scheint immer zurückzukehren true. Ebenso isClosed()scheint das Aufrufen eines Sockets, der remote geschlossen wurde, immer zurückzukehren false. Ich habe gelesen, dass, um tatsächlich festzustellen, ob ein Socket geschlossen wurde oder nicht, Daten in den Ausgabestream geschrieben und eine Ausnahme abgefangen werden muss. Dies scheint ein wirklich unreiner Weg zu sein, um mit dieser Situation umzugehen. Wir müssten nur ständig eine Müllnachricht über das Netzwerk spammen, um jemals zu erfahren, wann ein Socket geschlossen wurde.

Gibt es eine andere Lösung?

Dan Brouwer
quelle

Antworten:

183

Es gibt keine TCP-API, die den aktuellen Status der Verbindung angibt. isConnected()und isClosed()teilen Sie Ihnen den aktuellen Status Ihrer Steckdose mit . Nicht dasselbe.

  1. isConnected()sagt Ihnen , ob Sie angeschlossen haben diese Buchse . Sie haben, also gibt es true zurück.

  2. isClosed()sagt Ihnen, ob Sie diese Steckdose geschlossen haben. Bis Sie dies getan haben, wird false zurückgegeben.

  3. Wenn der Peer die Verbindung ordnungsgemäß geschlossen hat

    • read() gibt -1 zurück
    • readLine() kehrt zurück null
    • readXXX()wirft EOFExceptionfür jeden anderen XXX.

    • Ein Schreibvorgang löst Folgendes aus IOException: "Verbindung durch Peer zurückgesetzt", was zu Pufferverzögerungen führen kann.

  4. Wenn die Verbindung aus einem anderen Grund unterbrochen wurde IOException, löst ein Schreibvorgang schließlich einen aus, wie oben beschrieben, und ein Lesevorgang kann dasselbe tun.

  5. Wenn der Peer noch verbunden ist, aber die Verbindung nicht verwendet, kann ein Lesezeitlimit verwendet werden.

  6. Im Gegensatz zu dem, was Sie an anderer Stelle lesen, ClosedChannelExceptionsagt Ihnen dies nicht. [Auch nicht SocketException: socket closed.] Es sagt Ihnen , nur dass Sie den geschlossenen Kanal, und dann ging es weiter zu verwenden. Mit anderen Worten, ein Programmierfehler von Ihrer Seite. Es zeigt keine geschlossene Verbindung an.

  7. Als Ergebnis einiger Experimente mit Java 7 unter Windows XP scheint es auch, dass wenn:

    • Sie wählen auf OP_READ
    • select() gibt einen Wert größer als Null zurück
    • das zugehörige SelectionKeyist bereits ungültig ( key.isValid() == false)

    Dies bedeutet, dass der Peer die Verbindung zurückgesetzt hat. Dies kann jedoch entweder für die JRE-Version oder die Plattform spezifisch sein.

Marquis von Lorne
quelle
17
Es ist kaum zu glauben, dass das verbindungsorientierte TCP-Protokoll nicht einmal den Status seiner Verbindung kennen kann ... Fahren die Leute, die diese Protokolle entwickeln, ihre Autos mit geschlossenen Augen?
PedroD
31
@PedroD Im Gegenteil: Es war absichtlich. Frühere Protokollsuiten wie SNA hatten einen Wählton. TCP wurde entwickelt, um einen Atomkrieg und, was noch trivialer ist, die Höhen und Tiefen des Routers zu überstehen: daher das völlige Fehlen von Wählton, Verbindungsstatus usw.; und es ist auch der Grund, warum TCP Keepalive in den RFCs als kontroverses Feature beschrieben wird und warum es standardmäßig immer deaktiviert ist. TCP ist immer noch bei uns. SNA? IPX? ISO? Nicht. Sie haben es richtig gemacht.
Marquis von Lorne
3
Ich glaube nicht, dass dies eine gute Ausrede ist, um diese Informationen vor uns zu verbergen. Zu wissen, dass die Verbindung unterbrochen wurde, bedeutet nicht unbedingt, dass das Protokoll weniger fehlerresistent ist. Es hängt immer davon ab, was wir mit diesem Wissen machen. Für mich sind die Methoden isBound und isConnected von Java reine Scheinmethoden, sie haben keine Verwendung , aber drücken Sie die Notwendigkeit eines Listener für Verbindungsereignisse aus ... Aber ich wiederhole: Zu wissen, dass die Verbindung unterbrochen wurde, macht das Protokoll nicht schlechter. Wenn diese Protokolle, von denen Sie sagen, die Verbindung unterbrochen haben, sobald sie festgestellt haben, dass sie verloren gegangen ist, ist das eine andere Geschichte.
PedroD
21
@ Pedro Du verstehst nicht. Es ist keine "Entschuldigung". Es sind keine Informationen zurückzuhalten. Es gibt keinen Wählton. TCP weiß nicht, ob die Verbindung fehlgeschlagen ist, bis Sie versuchen, etwas dagegen zu tun. Das war das grundlegende Designkriterium.
Marquis von Lorne
2
@EJP danke für deine Antwort. Wissen Sie vielleicht, wie Sie überprüfen können, ob eine Steckdose geschlossen wurde, ohne zu blockieren? Die Verwendung von read oder readLine wird blockiert. Gibt es eine Möglichkeit zu erkennen, ob der andere Socket mit der verfügbaren Methode geschlossen wurde, er scheint 0statt zurückzukehren -1.
Insumity
9

In verschiedenen Messaging-Protokollen ist es allgemein üblich, sich gegenseitig zu schlagen (weiterhin Ping-Pakete zu senden). Das Paket muss nicht sehr groß sein. Mit dem Prüfmechanismus können Sie den nicht verbundenen Client erkennen, noch bevor TCP dies im Allgemeinen herausfindet (das TCP-Zeitlimit ist weitaus höher). Senden Sie eine Prüfung und warten Sie etwa 5 Sekunden auf eine Antwort, wenn Sie keine Antwort für beispielsweise 2-3 sehen Bei nachfolgenden Sonden wird Ihr Player getrennt.

Auch verwandte Frage

Kalpak Gadre
quelle
6
In einigen Protokollen ist dies "allgemeine Praxis" . Ich stelle fest, dass HTTP, das am häufigsten verwendete Anwendungsprotokoll auf dem Planeten, keine PING-Operation hat.
Marquis von Lorne
2
@ user207421, da HTTP / 1 ein One-Shot-Protokoll ist. Sie senden eine Anfrage, Sie erhalten eine Antwort. Und du bist fertig. Steckdose ist geschlossen. Die Websocket-Erweiterung verfügt über eine PING-Operation, ebenso wie HTTP / 2.
Michał Zabielski
Ich empfahl Herzschlag mit einem einzigen Byte, wenn möglich :)
Stefan Reich
@ MichałZabielski Es liegt daran, dass die Designer von HTTP es nicht spezifiziert haben. Sie wissen nicht warum nicht. HTTP / 1.1 ist kein One-Shot-Protokoll. Der Socket ist nicht geschlossen: HTTP-Verbindungen bleiben standardmäßig bestehen. FTP, SMTP, POP3, IMAP, TLS, ... haben keinen Herzschlag.
Marquis von Lorne
2

Ich sehe die andere Antwort, die gerade veröffentlicht wurde, aber ich denke, Sie sind interaktiv mit Kunden, die Ihr Spiel spielen, daher kann ich einen anderen Ansatz wählen (während BufferedReader in einigen Fällen definitiv gültig ist).

Wenn Sie möchten ... können Sie die Verantwortung für die Registrierung an den Kunden delegieren. Das heißt, Sie hätten eine Sammlung verbundener Benutzer mit einem Zeitstempel für die letzte von jedem empfangene Nachricht. Wenn ein Client eine Zeitüberschreitung aufweist, würden Sie eine erneute Registrierung des Clients erzwingen. Dies führt jedoch zu dem unten stehenden Zitat und der Idee.

Ich habe gelesen, dass, um tatsächlich festzustellen, ob ein Socket geschlossen wurde oder nicht, Daten in den Ausgabestream geschrieben werden müssen und eine Ausnahme abgefangen werden muss. Dies scheint ein wirklich unreiner Weg zu sein, um mit dieser Situation umzugehen.

Wenn Ihr Java-Code den Socket nicht geschlossen / getrennt hat, wie würden Sie dann darüber informiert, dass der Remote-Host Ihre Verbindung geschlossen hat? Letztendlich macht Ihr Versuch / Fang ungefähr das Gleiche wie ein Poller, der auf Ereignisse in der IST-Buchse wartet. Folgendes berücksichtigen:

  • Ihr lokales System könnte Ihren Socket schließen, ohne Sie zu benachrichtigen. Dies ist nur die Implementierung von Socket (dh es fragt die Hardware / Treiber / Firmware / was auch immer nicht nach Statusänderungen ab).
  • neuer Socket (Proxy p) ... es gibt mehrere Parteien (wirklich 6 Endpunkte), die die Verbindung zu Ihnen schließen könnten ...

Ich denke, eines der Merkmale der abstrahierten Sprachen ist, dass Sie von den Minutien abstrahiert sind. Denken Sie an das Schlüsselwort using in C # (try / finally) für SqlConnection s oder was auch immer ... es sind nur die Kosten für die Geschäftsabwicklung ... Ich denke, dass try / catch / finally das akzeptierte und notwendige Muster für die Verwendung von Socket ist.

Scottley
quelle
Ihr lokales System kann Ihren Socket nicht mit oder ohne Benachrichtigung schließen. Ihre Frage beginnt mit "Wenn Ihr Java-Code den Socket nicht geschlossen / getrennt hat ...?" macht auch keinen Sinn.
Marquis von Lorne
1
Was genau verhindert, dass das System (z. B. Linux) das Schließen des Sockets erzwingt? Ist es immer noch möglich, dies von 'gdb' aus mit dem Befehl 'call close' zu tun?
Andrey Lebedenko
@AndreyLebedenko Nichts "verhindert es genau", aber es tut es nicht.
Marquis von Lorne
@EJB wer macht das dann?
Andrey Lebedenko
@AndreyLebedenko Niemand macht es. Nur die Anwendung kann ihre eigenen Sockets schließen, es sei denn, sie wird ohne dies beendet. In diesem Fall wird das Betriebssystem bereinigt.
Marquis von Lorne
1

Ich denke, dies ist die Natur von TCP-Verbindungen. In diesen Standards dauert es ungefähr 6 Minuten, bis die Übertragung unterbrochen ist, bevor wir zu dem Schluss kommen, dass die Verbindung unterbrochen ist! Ich glaube nicht, dass Sie eine genaue Lösung für dieses Problem finden können. Vielleicht ist es besser, einen praktischen Code zu schreiben, um zu erraten, wann der Server annehmen sollte, dass eine Benutzerverbindung geschlossen ist.

Ehsan Khodarahmi
quelle
2
Es kann viel länger dauern, bis zu zwei Stunden.
Marquis von Lorne
0

Wie @ user207421 sagt, gibt es aufgrund des TCP / IP-Protokollarchitekturmodells keine Möglichkeit, den aktuellen Status der Verbindung zu ermitteln. Der Server muss Sie also bemerken, bevor Sie die Verbindung schließen, oder Sie überprüfen sie selbst.
Dies ist ein einfaches Beispiel, das zeigt, wie der Socket vom Server geschlossen wird:

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");
ucMedia
quelle
-2

So gehe ich damit um

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

wenn der result.code == null ist

Petar Ceho
quelle
Sie müssen nicht readLine()zweimal anrufen . Sie wissen bereits, dass es im elseBlock null war .
Marquis von Lorne
-4

Wenn Sie unter Linux in einen Socket schreiben (), den die andere, Ihnen unbekannte Seite geschlossen hat, wird ein SIGPIPE-Signal / eine Ausnahme ausgelöst, wie auch immer Sie es aufrufen möchten. Wenn Sie jedoch nicht vom SIGPIPE erfasst werden möchten, können Sie send () mit dem Flag MSG_NOSIGNAL verwenden. Der send () -Aufruf wird mit -1 zurückgegeben. In diesem Fall können Sie errno überprüfen, um zu erfahren, dass Sie versucht haben, eine defekte Pipe (in diesem Fall einen Socket) mit dem Wert EPIPE zu schreiben, der laut errno.h entspricht 32. Als Reaktion auf das EPIPE können Sie versuchen, den Socket erneut zu öffnen und Ihre Informationen erneut zu senden.

MikeK
quelle
Der send()Aufruf gibt nur dann -1 zurück, wenn die ausgehenden Daten lange genug gepuffert wurden, damit die Sendezeitgeber ablaufen. Aufgrund der Pufferung an beiden Enden und der Asynchronität send()unter der Haube wird dies beim ersten Senden nach dem Trennen mit ziemlicher Sicherheit nicht passieren .
Marquis von Lorne