Erkennen Sie sofort die Trennung des Clients vom Server-Socket


80

Wie kann ich feststellen, dass ein Client die Verbindung zu meinem Server getrennt hat?

Ich habe den folgenden Code in meiner AcceptCallBackMethode

static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
  //Accept incoming connection
  Socket listener = (Socket)ar.AsyncState;
  handler = listener.EndAccept(ar);
}

Ich muss einen Weg finden, um so schnell wie möglich festzustellen, dass der Client die Verbindung zum handlerSocket getrennt hat.

Ich habe es versucht:

  1. handler.Available;
  2. handler.Send(new byte[1], 0, SocketFlags.None);
  3. handler.Receive(new byte[1], 0, SocketFlags.None);

Die oben genannten Ansätze funktionieren, wenn Sie eine Verbindung zu einem Server herstellen und erkennen möchten, wenn die Verbindung zum Server getrennt wird. Sie funktionieren jedoch nicht, wenn Sie der Server sind und die Trennung des Clients erkennen möchten.

Jede Hilfe wird geschätzt.


10
@Samuel: Die TCP- und Verbindungs-Tags sind für diesen Beitrag sehr relevant, da TCP eine Verbindung aufrechterhält (während andere Netzwerkprotokolle wie UDP dies nicht tun).
Noldorin

3
Mehr zur Heartbeat-Lösung in meinem Blog: Erkennung von halboffenen (gelöschten) Verbindungen
Stephen Cleary

Die hier beschriebene Lösung funktioniert gut für mich: stackoverflow.com/questions/1387459/…
Rawk

Antworten:


109

Da keine Ereignisse verfügbar sind, die signalisieren, wenn die Steckdose getrennt ist, müssen Sie sie mit einer für Sie akzeptablen Frequenz abfragen.

Mit dieser Erweiterungsmethode können Sie eine zuverlässige Methode verwenden, um festzustellen, ob eine Steckdose nicht angeschlossen ist.

static class SocketExtensions
{
  public static bool IsConnected(this Socket socket)
  {
    try
    {
      return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
    }
    catch (SocketException) { return false; }
  }
}

1
Das hat funktioniert. Vielen Dank. Ich habe die Methode geändert, um zurückzukehren! (Socket.Available == 0 && socket.Poll (1, SelectMode.SelectRead)); weil ich vermute, Socket.Available ist schneller als Socket.Poll ()

27
Diese Methode funktioniert nur, wenn das andere Ende der Verbindung den Socket tatsächlich schließt / herunterfährt. Ein nicht angeschlossenes Netzwerk- / Stromkabel wird vor Ablauf der Zeitüberschreitung nicht bemerkt. Die einzige Möglichkeit, sofort über Verbindungsabbrüche informiert zu werden, besteht in einer Heartbeat-Funktion, mit der die Verbindung kontinuierlich überprüft werden kann.
Kasper Holdum

7
@ Smart Alec: Eigentlich solltest du das Beispiel wie oben gezeigt verwenden. Wenn Sie die Reihenfolge ändern, besteht eine mögliche Race-Bedingung: Wenn socket.Available0 zurückgegeben wird und Sie kurz vor dem socket.PollAufruf ein Paket erhalten , Pollwird true zurückgegeben und die Methode wird zurückgegeben false, obwohl der Socket tatsächlich noch fehlerfrei ist.
Groo

4
Dies funktioniert in 99% der Fälle gut, manchmal führt dies jedoch zu einer falschen Trennung.
Matthew Finlay

5
Genau wie Matthew Finlay bemerkt hat, werden hier manchmal falsche Verbindungsabbrüche gemeldet, da zwischen dem Ergebnis der Poll-Methode und der Überprüfung der Eigenschaft Available immer noch eine Race-Bedingung besteht. Ein Paket könnte fast zum Lesen bereit sein, aber noch nicht. Daher ist Verfügbar 0 - aber eine Millisekunde später müssen Daten gelesen werden. Eine bessere Option ist der Versuch, ein Byte und das SocketFlags.Peek-Flag zu empfangen. Oder implementieren Sie eine Form von Heartbeat und halten Sie den Verbindungsstatus auf einem höheren Niveau. Oder verlassen Sie sich auf die Fehlerbehandlung bei Ihren Sende- / Empfangsmethoden (& Rückrufe, wenn Sie die asynchronen Versionen verwenden).
mbargiel

22

Jemand erwähnte die KeepAlive-Fähigkeit von TCP Socket. Hier ist es schön beschrieben:

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

Ich benutze es so: Nachdem der Socket angeschlossen ist, rufe ich diese Funktion auf, die keepAlive aktiviert. Der keepAliveTimeParameter gibt das Zeitlimit in Millisekunden ohne Aktivität an, bis das erste Keep-Alive-Paket gesendet wird. Der keepAliveIntervalParameter gibt das Intervall in Millisekunden zwischen dem Senden aufeinanderfolgender Keep-Alive-Pakete an, wenn keine Bestätigung empfangen wird.

    void SetKeepAlive(bool on, uint keepAliveTime , uint keepAliveInterval )
    {
        int size = Marshal.SizeOf(new uint());

        var inOptionValues = new byte[size * 3];

        BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
        BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, size);
        BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);

        socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
    }

Ich verwende auch synchrones Lesen:

socket.BeginReceive(packet.dataBuffer, 0, 128,
                    SocketFlags.None, new AsyncCallback(OnDataReceived), packet);

Und beim Rückruf wird hier ein Timeout abgefangen SocketException, das ausgelöst wird, wenn der Socket nach dem Keep-Alive-Paket kein ACK-Signal erhält.

public void OnDataReceived(IAsyncResult asyn)
{
    try
    {
        SocketPacket theSockId = (SocketPacket)asyn.AsyncState;

        int iRx = socket.EndReceive(asyn);
    catch (SocketException ex)
    {
        SocketExceptionCaught(ex);
    }
}

Auf diese Weise kann ich die Trennung zwischen TCP-Client und -Server sicher erkennen.


6
Ja, setzen Sie das Keepalive + -Intervall auf einen niedrigen Wert. Alle Abfragen / Sendungen sollten nach Keepalive + 10 * -Intervall-Millisekunden fehlschlagen. Die 10 Wiederholungsversuche scheinen seit Vista fest codiert zu sein? Funktioniert auch, wenn Sie das Kabel im Gegensatz zu den meisten anderen Antworten abziehen. Dies sollte die akzeptierte Antwort sein.
Toster-CX

15

Das ist einfach nicht möglich. Es besteht keine physische Verbindung zwischen Ihnen und dem Server (außer in dem äußerst seltenen Fall, dass Sie zwei Computer mit einem Loopback-Kabel verbinden).

Wenn die Verbindung ordnungsgemäß geschlossen wird, wird die andere Seite benachrichtigt. Wenn die Verbindung jedoch auf andere Weise getrennt wird (z. B. wenn die Verbindung des Benutzers unterbrochen wird), weiß der Server dies erst, wenn das Zeitlimit überschritten ist (oder wenn versucht wird, in die Verbindung zu schreiben, und wenn die Bestätigung eine Zeitüberschreitung aufweist). So funktioniert TCP und man muss damit leben.

Daher ist "sofort" unrealistisch. Das Beste, was Sie tun können, ist innerhalb des Zeitlimits, das von der Plattform abhängt, auf der der Code ausgeführt wird.

BEARBEITEN: Wenn Sie nur nach ordnungsgemäßen Verbindungen suchen, senden Sie doch einfach von Ihrem Client aus den Befehl "DISCONNECT" an den Server.


1
Vielen Dank, aber ich meine eigentlich eine anmutige Software-Trennung, keine physische Trennung. Ich verwende Windows

1
Wenn er nur nach anmutigen Unterbrechungen sucht, muss er nichts senden. Seine Lektüre wird das Ende des Streams zurückgeben.
Marquis von Lorne

6

"So funktioniert TCP und man muss damit leben."

Ja, du hast recht. Es ist eine Tatsache des Lebens, die ich erkannt habe. Sie werden das gleiche Verhalten auch in professionellen Anwendungen sehen, die dieses Protokoll (und sogar in anderen) verwenden. Ich habe es sogar in Online-Spielen gesehen; Ihr Kumpel sagt "Auf Wiedersehen" und er scheint noch 1-2 Minuten online zu sein, bis der Server "das Haus aufräumt".

Sie können die hier vorgeschlagenen Methoden verwenden oder einen "Herzschlag" implementieren, wie ebenfalls vorgeschlagen. Ich wähle das erstere. Wenn ich mich jedoch für Letzteres entschieden hätte, hätte der Server einfach jeden Client von Zeit zu Zeit mit einem einzelnen Byte "pingen" lassen und prüfen müssen, ob wir eine Zeitüberschreitung oder keine Antwort haben. Sie können sogar einen Hintergrund-Thread verwenden, um dies mit genauem Timing zu erreichen. Vielleicht könnte sogar eine Kombination in eine Art Optionsliste (Enum-Flags oder so) implementiert werden, wenn Sie sich darüber wirklich Sorgen machen. Es ist jedoch keine große Sache, die Aktualisierung des Servers etwas zu verzögern, solange Sie die Aktualisierung durchführen. Es ist das Internet und niemand erwartet, dass es magisch ist! :) :)


3

Die Implementierung von Heartbeat in Ihr System könnte eine Lösung sein. Dies ist nur möglich, wenn sowohl Client als auch Server unter Ihrer Kontrolle stehen. Sie können ein DateTime-Objekt haben, das die Zeit verfolgt, zu der die letzten Bytes vom Socket empfangen wurden. Und nehmen Sie an, dass der Socket, der über ein bestimmtes Intervall nicht geantwortet hat, verloren geht. Dies funktioniert nur, wenn Heartbeat / Custom Keep Alive implementiert ist.


2

Ich habe ziemlich nützlich gefunden, eine weitere Problemumgehung dafür!

Wenn Sie asynchrone Methoden zum Lesen von Daten vom Netzwerk-Socket verwenden (ich meine, verwenden Sie BeginReceive- EndReceive Methoden), wenn eine Verbindung beendet wird; Eine der folgenden Situationen tritt auf: Entweder wird eine Nachricht ohne Daten gesendet (Sie können sie mit sehen Socket.Available- obwohl sie BeginReceiveausgelöst wird, ist ihr Wert Null) oder der Socket.ConnectedWert wird bei diesem Aufruf falsch (versuchen Sie EndReceivedann nicht, sie zu verwenden ).

Ich poste die Funktion, die ich verwendet habe. Ich denke, Sie können besser sehen, was ich damit gemeint habe:


private void OnRecieve(IAsyncResult parameter) 
{
    Socket sock = (Socket)parameter.AsyncState;
    if(!sock.Connected || sock.Available == 0)
    {
        // Connection is terminated, either by force or willingly
        return;
    }

    sock.EndReceive(parameter);
    sock.BeginReceive(..., ... , ... , ..., new AsyncCallback(OnRecieve), sock);

    // To handle further commands sent by client.
    // "..." zones might change in your code.
}

2

Dies hat bei mir funktioniert. Der Schlüssel ist, dass Sie einen separaten Thread benötigen, um den Socket-Status beim Abrufen zu analysieren. Wenn Sie dies im selben Thread wie der Socket tun, wird die Erkennung fehlgeschlagen.

//open or receive a server socket - TODO your code here
socket = new Socket(....);

//enable the keep alive so we can detect closure
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);

//create a thread that checks every 5 seconds if the socket is still connected. TODO add your thread starting code
void MonitorSocketsForClosureWorker() {
    DateTime nextCheckTime = DateTime.Now.AddSeconds(5);

    while (!exitSystem) {
        if (nextCheckTime < DateTime.Now) {
            try {
                if (socket!=null) {
                    if(socket.Poll(5000, SelectMode.SelectRead) && socket.Available == 0) {
                        //socket not connected, close it if it's still running
                        socket.Close();
                        socket = null;    
                    } else {
                        //socket still connected
                    }    
               }
           } catch {
               socket.Close();
            } finally {
                nextCheckTime = DateTime.Now.AddSeconds(5);
            }
        }
        Thread.Sleep(1000);
    }
}

1

Der Beispielcode hier http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx zeigt, wie Sie feststellen können, ob der Socket noch verbunden ist, ohne Daten zu senden.

Wenn Sie im Serverprogramm Socket.BeginReceive () aufgerufen haben und der Client die Verbindung "ordnungsgemäß" geschlossen hat, wird Ihr Empfangsrückruf aufgerufen und EndReceive () gibt 0 Byte zurück. Diese 0 Bytes bedeuten, dass der Client möglicherweise die Verbindung getrennt hat. Anschließend können Sie mithilfe der im MSDN-Beispielcode gezeigten Technik feststellen, ob die Verbindung geschlossen wurde.


1

Wenn Sie die Kommentare von mbargiel und mycelo zur akzeptierten Antwort erweitern, können Sie Folgendes mit einem nicht blockierenden Socket auf der Serverseite verwenden, um zu informieren, ob der Client heruntergefahren wurde.

Dieser Ansatz leidet nicht unter der Racebedingung, die die Poll-Methode in der akzeptierten Antwort beeinflusst.

// Determines whether the remote end has called Shutdown
public bool HasRemoteEndShutDown
{
    get
    {
        try
        {
            int bytesRead = socket.Receive(new byte[1], SocketFlags.Peek);

            if (bytesRead == 0)
                return true;
        }
        catch
        {
            // For a non-blocking socket, a SocketException with 
            // code 10035 (WSAEWOULDBLOCK) indicates no data available.
        }

        return false;
    }
}

Der Ansatz basiert auf der Tatsache, dass die Socket.ReceiveMethode unmittelbar nach dem Herunterfahren des Sockets durch das Remote-Ende Null zurückgibt und wir alle Daten daraus gelesen haben. Aus Socket.Receive-Dokumentation :

Wenn der Remote-Host die Socket-Verbindung mit der Shutdown-Methode beendet und alle verfügbaren Daten empfangen wurden, wird die Receive-Methode sofort abgeschlossen und gibt null Byte zurück.

Wenn Sie sich im nicht blockierenden Modus befinden und keine Daten im Protokollstapelpuffer verfügbar sind, wird die Empfangsmethode sofort abgeschlossen und eine SocketException ausgelöst.

Der zweite Punkt erklärt die Notwendigkeit des Try-Catch.

Die Verwendung des SocketFlags.PeekFlags lässt alle empfangenen Daten unberührt, damit ein separater Empfangsmechanismus sie lesen kann.

Das Obige funktioniert auch mit einem blockierenden Socket. Beachten Sie jedoch, dass der Code beim Empfangsanruf blockiert wird (bis Daten empfangen werden oder das Empfangszeitlimit abgelaufen ist, was wiederum zu a führt SocketException).


0

Können Sie nicht einfach Select verwenden?

Verwenden Sie select an einer angeschlossenen Steckdose. Wenn die Auswahl mit Ihrem Socket als Bereit zurückgegeben wird, der nachfolgende Empfang jedoch 0 Byte zurückgibt, bedeutet dies, dass der Client die Verbindung getrennt hat. AFAIK, das ist der schnellste Weg, um festzustellen, ob der Client die Verbindung getrennt hat.

Ich kenne C # nicht, also ignoriere es einfach, wenn meine Lösung nicht in C # passt (C # bietet jedoch Auswahl ) oder wenn ich den Kontext falsch verstanden habe.


0

Mit der Methode SetSocketOption können Sie KeepAlive festlegen, das Sie darüber informiert, wann ein Socket getrennt wird

Socket _connectedSocket = this._sSocketEscucha.EndAccept(asyn);
                _connectedSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);

http://msdn.microsoft.com/en-us/library/1011kecd(v=VS.90).aspx

Ich hoffe es hilft! Ramiro Rinaldi


2
Es wird nicht "Sie wissen lassen, wann eine Steckdose getrennt wird". Es wird erkennt es, schließlich, wenn der Keep-Alive - Timer abläuft. Standardmäßig ist es auf zwei Stunden eingestellt. Sie können das nicht als "wann immer" beschreiben. Sie können es auch nicht als "Lassen Sie es wissen" beschreiben: Sie müssen noch lesen oder schreiben, damit der Fehler erkannt wird. -1
Marquis von Lorne

0

Ich hatte das gleiche Problem, versuchen Sie dies:

void client_handler(Socket client) // set 'KeepAlive' true
{
    while (true)
    {
        try
        {
            if (client.Connected)
            {

            }
            else
            { // client disconnected
                break;
            }
        }
        catch (Exception)
        {
            client.Poll(4000, SelectMode.SelectRead);// try to get state
        }
    }
}

0

Dies ist in VB, aber es scheint gut für mich zu funktionieren. Es sucht nach einer 0-Byte-Rückgabe wie im vorherigen Beitrag.

Private Sub RecData(ByVal AR As IAsyncResult)
    Dim Socket As Socket = AR.AsyncState

    If Socket.Connected = False And Socket.Available = False Then
        Debug.Print("Detected Disconnected Socket - " + Socket.RemoteEndPoint.ToString)
        Exit Sub
    End If
    Dim BytesRead As Int32 = Socket.EndReceive(AR)
    If BytesRead = 0 Then
        Debug.Print("Detected Disconnected Socket - Bytes Read = 0 - " + Socket.RemoteEndPoint.ToString)
        UpdateText("Client " + Socket.RemoteEndPoint.ToString + " has disconnected from Server.")
        Socket.Close()
        Exit Sub
    End If
    Dim msg As String = System.Text.ASCIIEncoding.ASCII.GetString(ByteData)
    Erase ByteData
    ReDim ByteData(1024)
    ClientSocket.BeginReceive(ByteData, 0, ByteData.Length, SocketFlags.None, New AsyncCallback(AddressOf RecData), ClientSocket)
    UpdateText(msg)
End Sub

-1

Sie können auch die .IsConnected-Eigenschaft des Sockets überprüfen, wenn Sie eine Abfrage durchführen möchten.


2
Dies funktioniert nicht für eines der oben vorgestellten Szenarien (Server / Client)

1
Außerdem heißt die Eigenschaft .Connected.
Qwertie

Das erkennt keine willkürlichen Unterbrechungen. Sie müssen noch einige E / A ausführen.
Marquis von Lorne

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.