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 festzustellen, dass der Client die Verbindung zum handler
Socket getrennt hat.
Ich habe es versucht:
handler.Available;
handler.Send(new byte[1], 0, SocketFlags.None);
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.
Antworten:
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; } } }
quelle
socket.Available
0 zurückgegeben wird und Sie kurz vor demsocket.Poll
Aufruf ein Paket erhalten ,Poll
wird true zurückgegeben und die Methode wird zurückgegebenfalse
, obwohl der Socket tatsächlich noch fehlerfrei ist.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
keepAliveTime
Parameter gibt das Zeitlimit in Millisekunden ohne Aktivität an, bis das erste Keep-Alive-Paket gesendet wird. DerkeepAliveInterval
Parameter 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.
quelle
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.
quelle
"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! :) :)
quelle
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.
quelle
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 sehenSocket.Available
- obwohl sieBeginReceive
ausgelöst wird, ist ihr Wert Null) oder derSocket.Connected
Wert wird bei diesem Aufruf falsch (versuchen SieEndReceive
dann 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. }
quelle
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); } }
quelle
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.
quelle
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.Receive
Methode unmittelbar nach dem Herunterfahren des Sockets durch das Remote-Ende Null zurückgibt und wir alle Daten daraus gelesen haben. Aus Socket.Receive-Dokumentation :Der zweite Punkt erklärt die Notwendigkeit des Try-Catch.
Die Verwendung des
SocketFlags.Peek
Flags 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
).quelle
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.
quelle
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
quelle
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 } } }
quelle
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
quelle
Sie können auch die .IsConnected-Eigenschaft des Sockets überprüfen, wenn Sie eine Abfrage durchführen möchten.
quelle
Connected
Eigenschaft gibt den Status der Verbindung zum letzten Vorgang wieder . Keine Abstimmung.