it-swarm.com.de

Erkennen Sie sofort die Trennung des Clients vom Server-Socket

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

Ich habe den folgenden Code in meiner AcceptCallBack-Methode

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 herauszufinden, dass sich der Client vom handler Socket 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, wann der Server die Verbindung trennt, aber sie funktionieren nicht , wenn Sie der Server sind und eine Client-Verbindung erkennen möchten.

Jede Hilfe wird geschätzt.

65
Smart Alec

Da es keine Ereignisse gibt, die signalisiert werden können, wenn die Steckdose abgezogen ist, müssen Sie sie mit einer für Sie akzeptablen Frequenz abfragen.

Mit dieser Erweiterungsmethode können Sie zuverlässig feststellen, ob eine Steckdose getrennt 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; }
  }
}
95
Samuel

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

Wenn die Verbindung ordnungsgemäß geschlossen wird, wird die andere Seite benachrichtigt. Wenn die Verbindung jedoch auf eine andere Weise getrennt wird (z. B., dass die Verbindung der Benutzer unterbrochen wird), weiß der Server dies nicht, bis es ein Zeitlimit hat (oder versucht, auf die Verbindung zu schreiben und das Zeitlimit für die Bestätigung). So funktioniert TCP und Sie müssen damit leben.

Daher ist "sofort" unrealistisch. Das Beste, was Sie tun können, ist die Zeitüberschreitung, die von der Plattform abhängt, auf der der Code ausgeführt wird.

BEARBEITEN: Wenn Sie nur nach eleganten Verbindungen suchen, senden Sie einfach einen "DISCONNECT" -Befehl von Ihrem Client an den Server.

12

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

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

Ich verwende es so: Nachdem der Socket angeschlossen ist, rufe ich diese Funktion auf, die keepAlive aktiviert. Der Parameter keepAliveTime gibt das Zeitlimit in Millisekunden an. Es wird keine Aktivität ausgeführt, bis das erste Keep-Alive-Paket gesendet wird. Der Parameter keepAliveInterval gibt das Intervall in Millisekunden an, zwischen dem aufeinanderfolgende Keep-Alive-Pakete gesendet werden, 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 im Rückruf wird hier das Timeout SocketException abgefangen, das sich erhöht, wenn ein Socket nach einem 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.

11
Majak

"Genau so funktioniert TCP und Sie müssen damit leben."

Ja, du hast recht. Es ist eine Tatsache des Lebens, die ich erkannt habe. Sie werden dasselbe Verhalten auch bei professionellen Anwendungen sehen, die dieses Protokoll verwenden (und sogar andere). 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 "Haus reinigt".

Sie können die vorgeschlagenen Methoden hier verwenden oder einen "Heartbeat" implementieren, wie ebenfalls vorgeschlagen. Ich wähle das erstere. Wenn ich mich jedoch für das letztere entschieden habe, würde der Server jeden Client mit einem einzigen Byte von Zeit zu Zeit "ping" lassen und sehen, ob ein Timeout oder keine Antwort vorliegt. Sie können sogar einen Hintergrund-Thread verwenden, um dies mit einem genauen Timing zu erreichen. Vielleicht könnte sogar eine Kombination in einer Art Optionsliste (Enum-Flags oder etwas) implementiert werden, wenn Sie sich wirklich Sorgen machen. Es ist jedoch keine so große Sache, eine kleine Verzögerung bei der Aktualisierung des Servers zu haben, solange Sie diese Aktualisierung durchführen. Es ist das Internet und niemand erwartet, dass es magisch ist! :)

6
ATC

Das funktionierte für mich, der Schlüssel ist, dass Sie einen separaten Thread benötigen, um den Socket-Status mit Polling zu analysieren. Wenn dies im selben Thread wie der Socket durchgeführt wird, schlägt die Erkennung fehl.

//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);
    }
}
2
JJ_Coder4Hire

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

Wenn Sie asynchrone Methoden zum Lesen von Daten aus dem Netzwerk-Socket verwenden (dh BeginReceive - EndReceive-Methoden), wenn eine Verbindung abgebrochen wird; Eine der folgenden Situationen tritt auf: Entweder wird eine Nachricht ohne Daten gesendet (Sie können es mit Socket.Available sehen - obwohl BeginReceive ausgelöst wird, ist der Wert null) oder Socket.Connected wird in diesem Aufruf falsch (verwenden Sie nicht EndReceive dann).

Ich poste die Funktion, die ich verwendet habe, ich glaube, Sie können besser verstehen, 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
user244991

Die Implementierung von Heartbeat in Ihr System kann eine Lösung sein. Dies ist nur möglich, wenn sich sowohl Client als auch Server unter Ihrer Kontrolle befinden. Sie können ein DateTime-Objekt haben, das den Zeitpunkt erfasst, zu dem die letzten Bytes vom Socket empfangen wurden. Und davon ausgehen, dass der Socket nicht über einen bestimmten Zeitraum reagiert hat, geht verloren. Dies funktioniert nur, wenn Sie Heartbeat/Custom Keep Alive implementiert haben.

2
Niran

In Erwähnung der Kommentare von mbargiel und mycelo der akzeptierten Antwort kann das Folgende mit einem nicht blockierenden Socket auf der Serverseite verwendet werden, um zu informieren, ob der Client heruntergefahren ist.

Dieser Ansatz leidet nicht unter der Race-Bedingung, 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.Receive-Methode unmittelbar nach dem Herunterfahren des Sockets durch das entfernte 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 herunterfährt und alle verfügbaren Daten empfangen wurden, wird die Receive-Methode sofort beendet und null Byte zurückgegeben.

Wenn Sie sich im Nichtblockierungsmodus befinden und im Protokollstapelpuffer keine Daten verfügbar sind, wird die Receive-Methode sofort beendet und eine SocketException ausgelöst.

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

Die Verwendung des SocketFlags.Peek-Flag lässt alle empfangenen Daten unberührt, sodass ein separater Empfangsmechanismus lesen kann.

Das Obige funktioniert auch mit einem blocking - Socket. Beachten Sie jedoch, dass der Code beim Receive-Aufruf blockiert wird (bis Daten empfangen werden oder die Empfangszeitüberschreitung abläuft, was wiederum zu einer SocketException führt).

0
Ian

Mit der Methode SetSocketOption können Sie KeepAlive einstellen, um Sie darüber zu informieren, wann ein Socket getrennt wird

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

http://msdn.Microsoft.com/de-de/library/1011kecd(v=VS.90).aspx

Hoffe, es hilft! Ramiro Rinaldi

0
Rama

Kannst du nicht einfach Select wählen? 

Wählen Sie select an einer angeschlossenen Steckdose. Wenn die Auswahl mit dem 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 die Verbindung zum Client getrennt wurde.

Ich kenne C # nicht, also ignoriere einfach, ob meine Lösung nicht in C # passt (C # bietet select obwohl) oder wenn ich den Kontext falsch verstanden hätte.

0
Aditya Sehgal

ich hatte dasselbe Problem, probiere es aus: 

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
Senior Vector

Der Beispielcode hier http://msdn.Microsoft.com/de-de/library/system.net.sockets.socket.connected.aspx Zeigt, wie Sie feststellen, ob der Socket noch vorhanden ist verbunden, ohne Daten zu senden.

Wenn Sie Socket.BeginReceive () im Serverprogramm 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 die Verbindung "möglicherweise" getrennt hat. Sie können dann die im MSDN-Beispielcode gezeigte Technik verwenden, um festzustellen, ob die Verbindung geschlossen wurde.

0
Bassem