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

82

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

Tengo el siguiente código en mi AcceptCallBackmétodo

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 antes posible que el cliente se ha desconectado del handlerSocket.

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 se desconecta el servidor, pero no funcionan cuando usted es el servidor y desea detectar la desconexión del cliente.

Se agradecerá cualquier ayuda.

cHao
fuente
10
@Samuel: Las etiquetas TCP y de conexión son muy relevantes para esta publicación, ya que TCP mantiene una conexión (mientras que otros protocolos de red como UDP no).
Noldorin
3
Más en la solución de los latidos del corazón de mi blog: La detección de conexiones medio abiertas (dropped)
Stephen Cleary
La solución descrita aquí funciona bien para mí: stackoverflow.com/questions/1387459/…
Rawk

Respuestas:

110

Dado que no hay eventos disponibles para señalar cuando el enchufe está desconectado, deberá sondearlo a una frecuencia que sea aceptable para usted.

Con este método de extensión, puede tener un método confiable para detectar si un enchufe 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; }
  }
}
Samuel
fuente
1
Esto funcionó. Gracias. ¡Cambié el método para volver! (Socket.Available == 0 && socket.Poll (1, SelectMode.SelectRead)); porque sospecho que socket. Disponible es más rápido que Socket.Poll ()
28
Este método no funciona a menos que el otro extremo de la conexión realmente cierre / apague el enchufe. Un cable de red / alimentación desconectado no se notará antes del período de tiempo de espera. La única forma de recibir notificaciones de desconexiones de inmediato es con una función de latido para verificar continuamente la conexión.
Kasper Holdum
7
@Smart Alec: en realidad, debería usar el ejemplo como se muestra arriba. Existe una condición de carrera potencial si cambia el orden: si socket.Availabledevuelve 0 y recibe un paquete justo antes de que socket.Pollse llame, Polldevolverá verdadero y el método volverá false, aunque el socket todavía está en buen estado.
Groo
4
Esto funciona bien el 99% del tiempo, aunque a veces da una falsa desconexión.
Matthew Finlay
5
Tal como lo notó Matthew Finlay, esto reportará falsas desconexiones a veces ya que todavía hay una condición de carrera entre el resultado del método Poll y la verificación de la propiedad Disponible. Un paquete podría estar casi listo para leerse, pero aún no, por lo que Disponible es 0, pero un milisegundo más tarde, hay datos para leer. Una mejor opción es intentar recibir un byte y el indicador SocketFlags.Peek. O implemente algún tipo de latido y mantenga el estado de la conexión en un nivel superior. O confíe en el manejo de errores en sus métodos de envío / recepción (y devoluciones de llamada, si usa las versiones asíncronas).
mbargiel
22

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 que el socket está conectado, llamo a esta función, que activa keepAlive. El keepAliveTimeparámetro especifica el tiempo de espera, en milisegundos, sin actividad hasta que se envía el primer paquete Keep-Alive. El keepAliveIntervalparámetro especifica el intervalo, en milisegundos, entre el momento en que se envían paquetes de mantenimiento de vida sucesivos si no se recibe ningún reconocimiento.

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

También estoy usando lectura asincrónica:

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

Y en la devolución de llamada, aquí se captura el tiempo de espera SocketException, que aumenta cuando el socket no recibe la señal ACK después del paquete de mantenimiento.

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

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

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

Majak
fuente
6
Sí, establezca el intervalo keepalive + en un valor bajo, cualquier encuesta / envío debe fallar después de keepalive + 10 * intervalo milisegundos. Los 10 reintentos parecen estar codificados desde Vista? Funciona incluso si desenchufa el cable a diferencia de la mayoría de las otras respuestas. Esta debería ser la respuesta aceptada.
toster-cx
15

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 computadoras 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 del usuario se interrumpe), el servidor no lo sabrá hasta que se agote el tiempo de espera (o intente escribir en la conexión y se agote el tiempo de confirmación). Esa es la forma en que funciona TCP y tienes que vivir con él.

Por lo tanto, "instantáneamente" 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 enviar un comando "DESCONECTAR" al servidor desde su cliente?

Erik Funkenbusch
fuente
1
Gracias, pero en realidad me refiero a una elegante desconexión del software, no a una desconexión física. Estoy usando Windows
1
Si solo busca desconexiones elegantes, no tiene que enviar nada. Su lectura volverá al final de la secuencia.
user207421
6

"Así es como funciona TCP y tienes que vivir con él".

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 por otros 1-2 minutos hasta que el servidor "limpia la casa".

Puede utilizar los métodos sugeridos aquí o implementar un "latido", como también se sugiere. Elijo el primero. Pero si escogiera lo último, simplemente haría que el servidor hiciera "ping" a cada cliente de vez en cuando con un solo byte, y vería si tenemos un tiempo de espera o no hay respuesta. Incluso podría usar un hilo de fondo para lograr esto con una sincronización precisa. Tal vez incluso se podría implementar una combinación en algún tipo de lista de opciones (indicadores de enumeración o algo así) si realmente le preocupa. Pero no es tan importante tener un pequeño retraso en la actualización del servidor, siempre que lo actualice. ¡Es Internet y nadie espera que sea mágico! :)

ATC
fuente
3

Implementar latidos en su sistema podría ser una solución. Esto solo es posible si tanto el cliente como el servidor están bajo su control. Puede hacer que un objeto DateTime lleve un registro de la hora en que se recibieron los últimos bytes del socket. Y suponga que el socket que no respondió durante un cierto intervalo se pierde. Esto solo funcionará si ha implementado heartbeat / custom keep alive.

Niran
fuente
2

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

Si usa métodos asincrónicos para leer datos del conector de red (quiero decir, use BeginReceive- EndReceive métodos), siempre que se termine una conexión; Aparece una de estas situaciones: O se envía un mensaje sin datos (puede verlo con Socket.Available- aunque BeginReceivese active, su valor será cero) o el Socket.Connectedvalor se vuelve falso en esta llamada (no intente usarlo EndReceive).

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


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.
}
Şafak Gür
fuente
2

Esto funcionó para mí, la clave es que necesita 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);
    }
}
JJ_Coder4Hire
fuente
1

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 programa del servidor y luego el cliente cerró la conexión "correctamente", se llamará a su devolución de llamada de recepción y EndReceive () devolverá 0 bytes. Estos 0 bytes significan que el cliente "puede" haberse desconectado. A continuación, puede utilizar la técnica que se muestra en el código de ejemplo de MSDN para determinar con certeza si la conexión se cerró.

Bassem
fuente
1

Ampliando los comentarios de mbargiel y mycelo sobre la respuesta aceptada, lo siguiente se puede utilizar con un conector sin bloqueo en el extremo del servidor para informar si el cliente se ha apagado.

Este enfoque no sufre la condición de carrera que afecta al método Poll en la respuesta aceptada.

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

El enfoque se basa en el hecho de que el Socket.Receivemétodo devuelve cero inmediatamente después de que el extremo remoto apaga su socket y hayamos leído todos los datos. De Socket.Reciba documentación :

Si el host remoto cierra la conexión de Socket con el método Shutdown y se han recibido todos los datos disponibles, el método de recepción se completará inmediatamente y devolverá cero bytes.

Si está en modo sin bloqueo y no hay datos disponibles en el búfer de la pila de protocolos, el método de recepción se completará de inmediato y generará una SocketException.

El segundo punto explica la necesidad del try-catch.

El uso de la SocketFlags.Peekbandera deja intactos los datos recibidos para que los lea un mecanismo de recepción independiente.

Lo anterior también funcionará con un conector de bloqueo , pero tenga en cuenta que el código se bloqueará en la llamada de recepción (hasta que se reciban los datos o transcurra el tiempo de espera de recepción, lo que nuevamente da como resultado a SocketException).

Ian
fuente
0

¿No puedes usar Select?

Utilice select en un enchufe conectado. Si la selección regresa con su conector como Listo pero la recepción posterior devuelve 0 bytes, eso significa que el cliente desconectó la conexión. AFAIK, esa es la forma más rápida de determinar si el cliente se desconectó.

No sé C #, así que ignore si mi solución no encaja en C # ( aunque C # proporciona selección ) o si había entendido mal el contexto.

Aditya Sehgal
fuente
0

Usando el método SetSocketOption, podrá configurar KeepAlive que le avisará cuando un Socket se desconecte

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

Rama
fuente
2
No le "avisará cuando se desconecte un enchufe". Lo detectará , eventualmente, si el temporizador de mantener vivo expira. De forma predeterminada, está configurado en dos horas. No se puede describir como "cuando sea". Tampoco puede describirlo como 'avisarle a usted: todavía tiene que hacer una lectura o una escritura para que se detecte la falla. -1
user207421
0

Tuve el mismo problema, prueba esto:

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
        }
    }
}
Vector mayor
fuente
0

Esto está en VB, pero parece funcionar bien para mí. Busca un retorno de 0 bytes como la publicación anterior.

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
Ken0x34
fuente
-1

También puede verificar la propiedad .IsConnected del socket si fuera a sondear.

theG
fuente
2
Esto no funcionará para ninguno de los escenarios (servidor / cliente) que presenté anteriormente
1
Además, la propiedad se llama .Connected.
Qwertie
Eso no detecta desconexiones arbitrarias. Todavía tienes que hacer algunas E / S.
user207421