Detectar instantáneamente la desconexión del cliente del socket del servidor

¿Cómo puedo detectar que un cliente se haya desconectado de mi servidor?

Tengo el siguiente código en mi método AcceptCallBack

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

Necesito encontrar una manera de descubrir lo más pronto posible que el cliente se ha desconectado del handler Socket.

He intentado:

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

Los enfoques anteriores funcionan cuando se conecta a un servidor y desea detectar cuándo el servidor se desconecta, pero no funcionan cuando usted es el servidor y desea detectar la desconexión del cliente.

Cualquier ayuda será apreciada.

Como no hay eventos disponibles para señalar cuando el socket está desconectado, tendrá que sondearlo a una frecuencia aceptable para usted.

Usando este método de extensión, puede tener un método confiable para detectar si un socket está desconectado.

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

Esto simplemente no es posible. No hay conexión física entre usted y el servidor (excepto en el caso extremadamente raro en el que se conecta entre dos compuers con un cable de bucle invertido).

Cuando la conexión se cierra correctamente, se notifica al otro lado. Pero si la conexión se desconecta de alguna otra manera (digamos que la conexión de los usuarios se interrumpe), entonces el servidor no lo sabrá hasta que se agote el tiempo de espera (o intente escribir en la conexión y el tiempo de espera se agote). Así es como funciona TCP y tienes que vivir con eso.

Por lo tanto, “al instante” no es realista. Lo mejor que puede hacer es dentro del período de tiempo de espera, que depende de la plataforma en la que se ejecuta el código.

EDITAR: Si solo está buscando conexiones elegantes, ¿por qué no simplemente enviar un comando “DESCONECTAR” al servidor desde su cliente?

Alguien mencionó la capacidad keepAlive de TCP Socket. Aquí está muy bien descrito:

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

Lo estoy usando de esta manera: después de conectar el socket, estoy llamando a esta función, que activa KeepAlive. El parámetro keepAliveTime especifica el tiempo de espera, en milisegundos, sin actividad hasta que se envía el primer paquete keep-alive. El parámetro keepAliveInterval especifica el intervalo, en milisegundos, entre el momento en que se envían los sucesivos paquetes keep-alive si no se recibe acuse de recibo.

  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); } 

También estoy usando lectura sincrónica:

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

Y en la callback, aquí se encuentra el tiempo de espera atrapado SocketException , que aumenta cuando el socket no recibe la señal ACK después del paquete keep-alive.

 public void OnDataReceived(IAsyncResult asyn) { try { SocketPacket theSockId = (SocketPacket)asyn.AsyncState; int iRx = socket.EndReceive(asyn); catch (SocketException ex) { SocketExceptionCaught(ex); } } 

De esta forma, puedo detectar de manera segura la desconexión entre el cliente TCP y el servidor.

“Así es como funciona TCP y tienes que vivir con eso”.

Sí, tienes razón. Es un hecho de la vida que me he dado cuenta. Verá el mismo comportamiento exhibido incluso en aplicaciones profesionales que utilizan este protocolo (e incluso otros). Incluso lo he visto ocurrir en juegos en línea; tu amigo dice “adiós”, y parece estar en línea durante otros 1-2 minutos hasta que el servidor “limpia la casa”.

Puede usar los métodos sugeridos aquí, o implementar un “latido”, como también se sugirió. Elijo el primero Pero si yo elijo el último, simplemente haría que el servidor “ping” cada cliente de vez en cuando con un solo byte, y ver si tenemos un tiempo de espera o no hay respuesta. Incluso podría usar un hilo de fondo para lograr esto con un tiempo preciso. Tal vez incluso una combinación podría implementarse en algún tipo de lista de opciones (enum flags o algo así) si realmente está preocupado por ello. Pero no es un gran problema tener un pequeño retraso en la actualización del servidor, siempre y cuando se actualice. Es internet, ¡y nadie espera que sea mágico! 🙂

Implementar latidos en su sistema puede ser una solución. Esto solo es posible si tanto el cliente como el servidor están bajo su control. Puede tener un objeto DateTime que haga un seguimiento de la hora en que se recibieron los últimos bytes del socket. Y supongamos que el socket no responde durante un cierto intervalo se pierden. Esto solo funcionará si tiene instalado heartbeat / custom keep alive.

¡He encontrado bastante útil, otra solución para eso!

Si usa métodos asíncronos para leer datos del socket de la red (es decir, use los métodos BeginReceiveEndReceive ), siempre que finalice una conexión; aparece una de estas situaciones: o se envía un mensaje sin datos (se puede ver con Socket.Available , aunque BeginReceive se BeginReceive , su valor será cero) o el valor Socket.Connected vuelva falso en esta llamada (no lo haga). intente usar EndReceive luego).

Estoy publicando la función que utilicé, creo que puedes ver mejor lo que quise decir de ella:


 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. } 

Esto funcionó para mí, la clave es que necesitas un hilo separado para analizar el estado del socket con el sondeo. hacerlo en el mismo hilo que el socket falla la detección.

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

¿No puedes usar Select?

Use seleccionar en un socket conectado. Si la selección regresa con su socket como Ready pero la posterior Receive devuelve 0 bytes significa que el cliente desconectó la conexión. AFAIK, esa es la manera más rápida de determinar si el cliente se desconectó.

No sé C # así que simplemente ignore si mi solución no encaja en C # (sin embargo, C # proporciona selección ) o si no entendí bien el contexto.

El código de ejemplo aquí http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx muestra cómo determinar si el Socket todavía está conectado sin enviar ningún dato.

Si llamó a Socket.BeginReceive () en el progtwig de servidor y luego el cliente cerró la conexión “con gracia”, se llamará a su callback de recepción y EndReceive () devolverá 0 bytes. Estos 0 bytes significan que el cliente “puede” haberse desconectado. A continuación, puede usar la técnica que se muestra en el código de ejemplo de MSDN para determinar con seguridad si la conexión se cerró.

Usando el método SetSocketOption, podrás establecer KeepAlive que te avisará cada vez que se desconecte un 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

¡Espero eso ayude! Ramiro Rinaldi

También puede verificar la propiedad .IsConnected del socket si tuviera que sondear.