Jak mogę wykryć, że klient odłączył się od mojego serwera?
Mam następujący kod w mojej AcceptCallBack
metodzie
static Socket handler = null;
public static void AcceptCallback(IAsyncResult ar)
{
//Accept incoming connection
Socket listener = (Socket)ar.AsyncState;
handler = listener.EndAccept(ar);
}
Muszę znaleźć sposób, aby jak najszybciej odkryć, że klient odłączył się od handler
gniazda.
Próbowałem:
handler.Available;
handler.Send(new byte[1], 0, SocketFlags.None);
handler.Receive(new byte[1], 0, SocketFlags.None);
Powyższe podejścia działają, gdy łączysz się z serwerem i chcesz wykryć, kiedy serwer się rozłącza, ale nie działają, gdy jesteś serwerem i chcesz wykryć rozłączenie klienta.
Każda pomoc zostanie doceniona.
Odpowiedzi:
Ponieważ nie ma dostępnych zdarzeń, które sygnalizowałyby odłączenie gniazda, będziesz musiał odpytywać je na akceptowalnej częstotliwości.
Korzystając z tej metody rozszerzenia, możesz mieć niezawodną metodę wykrywania, czy gniazdo jest odłączone.
static class SocketExtensions { public static bool IsConnected(this Socket socket) { try { return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); } catch (SocketException) { return false; } } }
źródło
socket.Available
zwróci 0 i otrzymasz pakiet tuż przedsocket.Poll
wywołaniem,Poll
zwróci wartość true i metoda zwróci wartośćfalse
, chociaż gniazdo jest nadal w dobrym stanie.Ktoś wspomniał o możliwościach KeepAlive w TCP Socket. Tutaj jest to ładnie opisane:
http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
Używam go w ten sposób: po podłączeniu gniazda wywołuję tę funkcję, która włącza keepAlive.
keepAliveTime
Parametr określa czas oczekiwania w milisekundach braku aktywności aż pierwszy pakiet keep-alive zostanie wysłana.keepAliveInterval
Parametr określa odstęp w milisekundach między kiedy kolejne pakiety keep-alive są wysyłane jeśli otrzymano żadnego potwierdzenia.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)keepAliveTime).CopyTo(inOptionValues, size); BitConverter.GetBytes((uint)keepAliveInterval).CopyTo(inOptionValues, size * 2); socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); }
Używam również czytania asynchronicznego:
socket.BeginReceive(packet.dataBuffer, 0, 128, SocketFlags.None, new AsyncCallback(OnDataReceived), packet);
W wywołaniu zwrotnym jest tu przechwytywany limit czasu
SocketException
, który wzrasta, gdy gniazdo nie otrzymuje sygnału ACK po pakiecie utrzymywania aktywności.public void OnDataReceived(IAsyncResult asyn) { try { SocketPacket theSockId = (SocketPacket)asyn.AsyncState; int iRx = socket.EndReceive(asyn); } catch (SocketException ex) { SocketExceptionCaught(ex); } }
W ten sposób jestem w stanie bezpiecznie wykryć rozłączenie między klientem TCP a serwerem.
źródło
To jest po prostu niemożliwe. Nie ma fizycznego połączenia między Tobą a serwerem (z wyjątkiem niezwykle rzadkich przypadków, gdy łączysz się między dwoma komputerami za pomocą kabla sprzężenia zwrotnego).
Gdy połączenie zostanie prawidłowo zamknięte, druga strona jest powiadamiana. Ale jeśli połączenie zostanie zerwane w inny sposób (powiedzmy, że połączenie użytkowników zostanie zerwane), serwer nie będzie wiedział, dopóki nie wygaśnie (lub spróbuje zapisać połączenie i upłynie limit czasu potwierdzenia). Tak właśnie działa TCP i trzeba z tym żyć.
Dlatego „natychmiastowe” jest nierealne. Najlepsze, co możesz zrobić, to zachować limit czasu, który zależy od platformy, na której działa kod.
EDYCJA: Jeśli szukasz tylko dobrych połączeń, to dlaczego nie wysłać po prostu polecenia „ROZŁĄCZ” do serwera ze swojego klienta?
źródło
„Po prostu tak działa TCP i trzeba z tym żyć”.
Tak, masz rację. Zrozumiałem, że to fakt życia. Zobaczysz to samo zachowanie nawet w profesjonalnych aplikacjach używających tego protokołu (a nawet innych). Widziałem to nawet w grach online; Twój kumpel mówi „do widzenia” i wydaje się być online przez kolejne 1-2 minuty, aż serwer „sprząta dom”.
Możesz użyć sugerowanych metod tutaj lub zaimplementować „bicie serca”, jak również zasugerowano. Wybieram to pierwsze. Ale gdybym wybrał to drugie, po prostu kazałbym serwerowi „pingować” każdego klienta co jakiś czas jednym bajtem i sprawdzać, czy mamy limit czasu, czy nie ma odpowiedzi. Możesz nawet użyć wątku w tle, aby osiągnąć to dzięki precyzyjnemu czasowi. Może nawet kombinacja mogłaby zostać zaimplementowana na jakiejś liście opcji (flagi wyliczenia lub coś takiego), jeśli naprawdę się tym martwisz. Ale to nie jest takie duże opóźnienie w aktualizacji serwera, o ile aktualizujesz. To internet i nikt nie oczekuje, że będzie magiczny! :)
źródło
Wdrożenie pulsu w systemie może być rozwiązaniem. Jest to możliwe tylko wtedy, gdy zarówno klient, jak i serwer są pod Twoją kontrolą. Możesz mieć obiekt DateTime, który śledzi czas odebrania ostatnich bajtów z gniazda. I załóżmy, że gniazdo, które nie odpowiedziało w określonym przedziale czasu, zostanie utracone. To zadziała tylko wtedy, gdy zaimplementowano bicie serca / niestandardowe utrzymywanie aktywności.
źródło
Znalazłem całkiem przydatne, kolejne obejście tego problemu!
Jeśli używasz asynchronicznych metod do odczytu danych z gniazda sieciowego (mam na myśli
BeginReceive
-EndReceive
metody), zawsze, gdy połączenie zostanie zakończone; pojawia się jedna z tych sytuacji: albo wiadomość jest wysyłana bez danych (możesz to zobaczyćSocket.Available
- nawet jeśliBeginReceive
jest wyzwalana, jej wartość będzie wynosić zero) lubSocket.Connected
wartość stanie się fałszywa w tym wywołaniu (nie próbujEndReceive
wtedy używać ).Piszę funkcję, z której korzystałem, myślę, że możesz lepiej zobaczyć, o co mi chodziło:
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. }
źródło
To zadziałało dla mnie, kluczem jest to, że potrzebujesz osobnego wątku do analizy stanu gniazda z odpytywaniem. robi to w tym samym wątku, w którym gniazdo nie jest wykrywane.
//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); } }
źródło
Przykładowy kod tutaj http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx pokazuje, jak określić, czy Socket jest nadal połączony bez wysyłania żadnych danych.
Jeśli wywołałeś Socket.BeginReceive () w programie serwera, a następnie klient zamknął połączenie "wdzięcznie", zostanie wywołane wywołanie zwrotne odbierania, a EndReceive () zwróci 0 bajtów. Te 0 bajtów oznacza, że klient „mógł” się rozłączyć. Następnie możesz użyć techniki pokazanej w przykładowym kodzie MSDN, aby ustalić na pewno, czy połączenie zostało zamknięte.
źródło
Rozwijając komentarze mbargiela i mycelo na temat zaakceptowanej odpowiedzi, można użyć poniższego z gniazdem nieblokującym po stronie serwera, aby poinformować, czy klient się wyłączył.
Takie podejście nie wpływa na stan wyścigu, który wpływa na metodę Sonda w zaakceptowanej odpowiedzi.
// 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; } }
Podejście opiera się na fakcie, że
Socket.Receive
metoda zwraca zero natychmiast po tym, jak zdalny koniec zamknie swoje gniazdo i odczytaliśmy z niego wszystkie dane. Od Socket.Receive dokumentacji :Drugi punkt wyjaśnia potrzebę próby złapania.
Użycie
SocketFlags.Peek
flagi pozostawia wszystkie odebrane dane nietknięte, aby umożliwić ich odczytanie przez oddzielny mechanizm odbioru.Powyższe będzie działać również z gniazdem blokującym , ale należy pamiętać, że kod będzie blokował wywołanie Receive (do momentu odebrania danych lub upływu limitu czasu odbioru, co ponownie spowoduje a
SocketException
).źródło
Nie możesz po prostu użyć Select?
Użyj opcji wyboru na podłączonym gnieździe. Jeśli wybór wraca z gniazdem jako Gotowe, ale kolejne Odbiór zwraca 0 bajtów, co oznacza, że klient rozłączył połączenie. AFAIK, to najszybszy sposób określenia, czy klient się rozłączył.
Nie znam języka C #, więc po prostu zignoruj, jeśli moje rozwiązanie nie pasuje do C # (C # zapewnia jednak wybór ) lub jeśli źle zrozumiałem kontekst.
źródło
Używając metody SetSocketOption, będziesz mógł ustawić KeepAlive, który poinformuje Cię o rozłączeniu Socket
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
Mam nadzieję, że to pomoże! Ramiro Rinaldi
źródło
miałem ten sam problem, spróbuj tego:
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 } } }
źródło
To jest w VB, ale wydaje mi się, że działa dobrze. Wygląda na 0 bajtów powrotu, tak jak w poprzednim wpisie.
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
źródło
Możesz również sprawdzić właściwość .IsConnected gniazda, jeśli chcesz sondować.
źródło
Connected
nieruchomości odzwierciedla stan połączenia na dzień ostatniej operacji . Brak odpytywania.