2011-10-04 6 views
9

He estado peleando con un problema durante 3 días enteros No encuentro ninguna solución, por favor ayuda :)
Trabajo con Visual Studio 2010 y C# language.¿Cómo se prueba una conexión interrumpida de TCPClient después de conectarse?

Tengo un dispositivo que funciona como un servidor, que envía algunos datos en períodos muy irregulares (no es posible definir ningún tiempo de espera de lectura).
Escribí un cliente TCP para conectarme a ese servidor y leer datos. Funciona bien, sin embargo, cuando algo falla en la red y el servidor deja de estar disponible (por ejemplo, cuando desconecto el cable de red de mi computadora), la aplicación tarda unos 10 segundos en "notar" que no hay conexión con el servidor y lanzar una excepción. (No sé por qué exactamente 10 segundos? ¿Dónde está definido? ¿Puedo cambiarlo?)
Quiero reaccionar más rápido, digamos después de un segundo después de la conexión rota.
Google para la respuesta, sin embargo, no me proporciona ninguna solución de trabajo.

El código de prueba está debajo, intento hacerlo en 2 hilos: uno está leyendo datos, el segundo está buscando el estado de la conexión y debería alertarme cuando está roto. No está funcionando ni para TCPClient ni para la clase Socket. Intenté leer/escribir algunos datos con tcpClient.SendTimeout = 100; y stream.WriteTimeout = 100;, pero parece que no funciona.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 

namespace TCPClient 
{ 
    class Program 
    { 
     static volatile bool _continue = true; 
     static TcpClient tcpClient; 
     static NetworkStream stream; 

     static void Main(string[] args) 
     { 
      try 
      { 
       //client thread - listen from server 
       Thread tcpListenThread = new Thread(TcpListenThread); 
       tcpListenThread.Start(); 

       //connection checking thread 
       Thread keepAliveThread = new Thread(KeepAliveThread); 
       keepAliveThread.Start(); 

       while (_continue) 
       { 
        if (Console.ReadLine() == "q") 
        { 
         _continue = false; 
        } 
       } 

       keepAliveThread.Join(2000); 
       if (keepAliveThread.IsAlive) 
       { 
        Console.WriteLine("Thread keepAlive has been aborted..."); 
        keepAliveThread.Abort(); 
       } 

       tcpListenThread.Join(2000); 
       if (tcpListenThread.IsAlive) 
       { 
        Console.WriteLine("Listen thread has been aborted..."); 
        tcpListenThread.Abort(); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("\n" + ex.Message); 
      } 

      Console.WriteLine("\nHit any key to quit..."); 
      Console.Read(); 
     } 

     private static void TcpListenThread() 
     { 
      string server = "172.20.30.40"; 
      int port = 3000; 

      try 
      { 
       using (tcpClient = new TcpClient()) 
       { 
        tcpClient.Connect(server, port); 

        if (tcpClient.Connected) 
         Console.WriteLine("Successfully connected to server"); 

        using (stream = tcpClient.GetStream()) 
        { 

         while (_continue) 
         { 
          Byte[] data = new Byte[1024]; 
          Int32 bytes = stream.Read(data, 0, data.Length); 
          string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes); 

          Console.WriteLine("Received: {0}, responseData); 
         } 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Listen thread exception! " + ex.Message); 
      } 
     } 


     private static void KeepAliveThread() 
     { 
      while (_continue) 
      { 
       if (tcpClient != null) 
       { 
        try 
        { 
         //...what to put here to check or throw exception when server is not available??... 
        } 
        catch (Exception ex) 
        { 
         Console.WriteLine("Disconnected..."); 
        } 
       } 

       Thread.Sleep(1000); //test for connection every 1000ms 
      } 
     } 
    } 
} 

Editar:
@ respuesta Carsten: a pesar de que parece prometedor, esta solución no funciona ...
Hice la aplicación de prueba más simple para ello:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 

namespace TCPClientTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       string server = "172.20.30.40"; 
       int port = 3000; 

       using (TcpClient tcpClient = new TcpClient()) 
       { 
        tcpClient.Connect(server, port); 

        int i = 0; 
        while (true) 
        { 
         // This is how you can determine whether a socket is still connected. 
         bool blockingState = tcpClient.Client.Blocking; 
         try 
         { 
          byte[] tmp = new byte[1]; 

          tcpClient.Client.Blocking = false; 
          tcpClient.Client.Send(tmp, 0, 0); 
          Console.WriteLine("Connected!"); 
         } 
         catch (SocketException e) 
         { 
          // 10035 == WSAEWOULDBLOCK 
          if (e.NativeErrorCode.Equals(10035)) 
           Console.WriteLine("Still Connected, but the Send would block"); 
          else 
          { 
           Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode); 
          } 
         } 
         finally 
         { 
          tcpClient.Client.Blocking = blockingState; 
         } 

         Console.WriteLine("Connected: {0} ({1})", tcpClient.Client.Connected, i++); 

         Thread.Sleep(1000); 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Global exception: {0}", ex.Message); 
      } 
     } 
    } 
} 

Los resultados están siguiendo, muestra:

Connected!
Conectado: Verdadero

más mi número de pedido cada un segundo. Cuando desconecto el cable de red, toma 8 segundos comenzar a imprimir:

Desconectado: código de error 10054!
Conectado: Falso

por 8 segundos No estoy al tanto de que se haya perdido la conexión. Parece que hacer ping es la mejor opción aquí, pero probaré otras soluciones.

+0

Posible duplicado: [¿Cómo puedo comprobar si un conector (TCP) está (des) conectado en C#?] (Http://stackoverflow.com/questions/515458/how-can-i- check-whether-a -tcp-socket-is-disconnected-in-c) – ladenedge

+2

@ladenedge Posible duplicado ... excepto que tiene más código de ejemplo para ayudar a otras personas a aprender de una manera diferente.Sé que su comentario es antiguo, sin embargo, es frustrante cuando personas como usted hacen comentarios inútiles y desalentadores que no son productivos. – Euthyphro

Respuesta

6

Respuesta simple. No puedes. Tcp está hecho de una manera que no permite esto. Sin embargo, la forma normal de lograr esto es enviar ping's con un intervalo más corto que los mensajes. Por lo tanto, supongamos que cada vez que recibe un mensaje del servidor, inicia un reloj que cuenta hacia atrás 1 minuto y luego envía un comando "ping" (si no ha recibido un mensaje nuevo entre medio). Si no recibe una respuesta a su "ping" en 30 segundos, concluye que la conexión está interrumpida.

Teóricamente, debería poder hacer esto en un nivel de paquete (tcp envía ACK que debería poder verificar), pero no sé si eso es posible en C#, o cualquier lenguaje de programación para eso importa, o si necesita hacer eso en firmware/hardware.

+0

Gracias por la respuesta. Hasta ahora, parece que hacer ping es la mejor opción, sin embargo, cuando busco la respuesta, he encontrado algunas páginas donde se señaló como una mala solución, no sé por qué. – mj82

+0

Creo que estás entendiendo mal algo, o lo estoy (lo cual es muy posible por cierto xD). Por lo que yo entiendo, lo que acabas de actualizar tu publicación ** es ** ping, solo que de una manera más inteligente que hacerlo tú mismo. Además, 8 segundos hasta que descubras que una desconexión no suena mal en absoluto. Los servidores IRC, por ejemplo, usan hasta 5 minutos entre pings (!). – Alxandr

+0

He actualizado mi publicación para mostrar que la solución propuesta por Carsten König no funciona. Intenta hacer 'tcpClient.Client.Send (...)' cada segundo, así que se esperaba que después de que el cable se desconectara lanzaría una excepción o algo después de un segundo, pero no es así. Por otro lado, funcionará de forma ping "normal", con clase Ping. Por lo tanto, mi publicación de actualización no es igual a ping :) – mj82

0

Hay una opción de socket llamada SO_KEEPALIVE de la implementación de Unix oginal que tiene el protocolo TCP enviar sondas ocasionales para asegurarse de que el otro extremo de la tubería sigue escuchando. Se puede configurar junto con la frecuencia con la que las sondas se configuran mediante Socket.IOControl

+0

Eh, KEEPALIVEs son escamosas en el mejor de los casos. A veces pueden causar problemas de los que resuelven, simplemente no recuerdo el motivo o la fuente, pero deben usarse con cuidado. –

14

Creo que esta es una pregunta que a menudo se presenta. Esto podría ser la razón por docs MSDN realmente dar una buena respuesta a esta - ver Socket.Connected

Cita de allí:

la propiedad connected recibe el estado de conexión del zócalo como de la última operación de E/S. Cuando devuelve falso, el zócalo ya sea nunca se conectó o ya no está conectado.

El valor de la propiedad Conectado refleja el estado de la conexión a partir de la operación más reciente. Si necesita determinar el estado actual de la conexión, realice una llamada que no sea de bloqueo y de cero bytes. Si la llamada vuelve con éxito o arroja un código de error WAEWOULDBLOCK (10035), entonces el socket sigue conectado; de lo contrario, la toma ya no está conectada.

con este código de ejemplo:

// .Connect throws an exception if unsuccessful 
client.Connect(anEndPoint); 

// This is how you can determine whether a socket is still connected. 
bool blockingState = client.Blocking; 
try 
{ 
    byte [] tmp = new byte[1]; 

    client.Blocking = false; 
    client.Send(tmp, 0, 0); 
    Console.WriteLine("Connected!"); 
} 
catch (SocketException e) 
{ 
    // 10035 == WSAEWOULDBLOCK 
    if (e.NativeErrorCode.Equals(10035)) 
     Console.WriteLine("Still Connected, but the Send would block"); 
    else 
    { 
     Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode); 
    } 
} 
finally 
{ 
    client.Blocking = blockingState; 
} 

Console.WriteLine("Connected: {0}", client.Connected); 

y la motification recta hacia adelante para una extensión método:

public static bool IsConnected(this Socket client) 
{ 
    // This is how you can determine whether a socket is still connected. 
    bool blockingState = client.Blocking; 
    try 
    { 
     byte [] tmp = new byte[1]; 

     client.Blocking = false; 
     client.Send(tmp, 0, 0); 
     return true; 
    } 
    catch (SocketException e) 
    { 
     // 10035 == WSAEWOULDBLOCK 
     if (e.NativeErrorCode.Equals(10035)) 
      return true; 
     else 
     { 
      return false; 
     } 
    } 
    finally 
    { 
     client.Blocking = blockingState; 
    } 
} 
+0

Si leo este derecho, ¿eso significa (básicamente) que al hacer 'Enviar (nuevo byte [0])' el método de envío espera que el 'ACK' subyacente llegue como respuesta al paquete enviado? – Alxandr

+0

@Carsten König: Gracias por su respuesta. Parece prometedor, pero me temo que no funciona. He editado mi publicación para explicar el problema con esta solución. – mj82

0

será correcta y trabajando si escribe lo siguiente:

byte[] tmp = new byte[] {0}; 
...    
client.Send(tmp, 1, 0); 
... 
5

Este es un hilo muy viejo, pero es el f rimero SO post que le ocurrió cuando buscaba esta pregunta y he encontrado una solución más útil en otro lugar, así que pensé que había puesto un enlace a ella para ayudar a otros en el futuro:

https://social.msdn.microsoft.com/Forums/en-US/c857cad5-2eb6-4b6c-b0b5-7f4ce320c5cd/c-how-to-determine-if-a-tcpclient-has-been-disconnected?forum=netfxnetcom&prof=required

ElFenix publicado esta respuesta que funcionó para mí:

// Detect if client disconnected 
if (tcp.Client.Poll(0, SelectMode.SelectRead)) 
{ 
    byte[] buff = new byte[1]; 
    if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0) 
    { 
    // Client disconnected 
    bClosed = true; 
    } 
} 
0

Por si acaso alguien más necesita algo simple y efectivo.

Este es el código que se me ocurrió

  while (true) 
      { 

       string s = null; 
       DateTime readLag = DateTime.Now; 
       try 
       { 
        s = streamIn.ReadLine(); 
       } 
       catch (Exception ex) 
       { 
        SocketException sockEx = ex.InnerException as SocketException; 
        if (sockEx != null) 
        { 
         OnDebug("(" + sockEx.NativeErrorCode + ") Exception = " + ex); 
        } 
        else 
        { 
         OnDebug("Not SockEx :" + ex); 
        } 
       } 



       if (enableLog) OnDebug(s); 
       if (s == null || s == "") 
       { 
        if (readLag.AddSeconds(.9) > DateTime.Now) 
        { 
         break; 
        } 
       } 
       else 
       { 
        CommandParser(s); 
       } 
      } 

Lo que obtuve fue error nativo 10035 cada vez que una lectura fracasó, pero bloquearía.

Cuando la conexión se destruyó, recibí de inmediato un retorno sin datos.

Establecer el readLag.AddSeconds() justo por debajo de mi tiempo de espera de lectura me daría una buena idea de que el tiempo nunca transcurrió y no obtuve datos. Lo cual no debería suceder.

En cuanto apareció este criterio, simplemente salgo del ciclo y el hilo termina.

Espero que esto ayude a alguien más por ahí.

Cuestiones relacionadas