2012-03-14 12 views
9

Un breve resumen de la situación:¿Es posible detectar si un Stream ha sido cerrado por el cliente?

Tengo un servicio que toma la información y envía respuestas a cabo a través de sockets. Las conexiones no son seguras. Quiero configurar otro servicio que pueda proporcionar TLS a estas conexiones: este nuevo servicio proporcionará un solo puerto y distribuirá las conexiones según el certificado del cliente proporcionado. No quiero usar stunnel por un par de razones, una de ellas es que requeriría un puerto de reenvío por puerto de recepción.

La solución actualmente estoy tratando de poner en práctica:

Esencialmente, estoy tratando de acoplar un SslStream (entrante) con un NetworkStream (saliente - que podría ser un zócalo, pero ponerlo en una NetworkStream para que coincida con el entrante) y tienen las operaciones de lectura/escritura vinculadas para los dos. Este enlace proporcionaría el flujo entre el cliente (a través de SSL/TLS) y el servicio (a través de una conexión no segura).

Aquí está la clase que se me ocurrió para enlazar estas corrientes:

public class StreamConnector 
{ 
    public StreamConnector(Stream s1, Stream s2) 
    { 
     StreamConnectorState state1 = new StreamConnectorState(s1, s2); 
     StreamConnectorState state2 = new StreamConnectorState(s2, s1); 
     s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1); 
     s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2); 
    } 

    private void ReadCallback(IAsyncResult result) 
    { 
     // Get state object. 
     StreamConnectorState state = (StreamConnectorState)result.AsyncState; 

     // Finish reading data. 
     int length = state.InStream.EndRead(result); 

     // Write data. 
     state.OutStream.Write(state.Buffer, 0, length); 

     // Wait for new data. 
     state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state); 
    } 
} 

public class StreamConnectorState 
{ 
    private const int BYTE_ARRAY_SIZE = 4096; 

    public byte[] Buffer { get; set; } 
    public Stream InStream { get; set; } 
    public Stream OutStream { get; set; } 

    public StreamConnectorState(Stream inStream, Stream outStream) 
    { 
     Buffer = new byte[BYTE_ARRAY_SIZE]; 
     InStream = inStream; 
     OutStream = outStream; 
    } 
} 

El problema:

Cuando el cliente se realiza el envío de información y dispone de la SslStream, el servidor no lo hace tener algún tipo de indicación de si esto ha sucedido o no. Esta clase de StreamConnector felizmente sigue corriendo hacia la eternidad sin lanzar ningún tipo de error, y no puedo encontrar ningún indicador de que deba detenerse. (Hay, por supuesto, el hecho de que obtengo 0 de longitud cada vez en ReadCallback, pero necesito poder proporcionar conexiones de larga duración, así que esta no es una buena forma de juzgar.)

Otro potencial El problema es que se llama a ReadCallback incluso si no hay datos disponibles. No estoy seguro si eso sería diferente si estuviese usando un Socket directamente en lugar de una transmisión, pero parece ineficaz seguir corriendo ese código una y otra vez.

Mis preguntas:

1) ¿Hay una manera de saber si una corriente se ha cerrado desde el lado del cliente?

2) ¿Hay una mejor manera de hacer lo que estoy tratando de hacer?

2a) ¿Existe alguna forma más eficiente de ejecutar el ciclo de lectura/escritura asíncrona?

EDITAR: Gracias, Robert. Resulta que se siguió llamando al bucle porque no estaba cerrando las transmisiones (debido a que no sabía cómo saber cuándo era necesario cerrar las transmisiones). Estoy incluyendo la solución de código completo en caso de que otra persona se encuentra con este problema:

/// <summary> 
/// Connects the read/write operations of two provided streams 
/// so long as both of the streams remain open. 
/// Disposes of both streams when either of them disconnect. 
/// </summary> 
public class StreamConnector 
{ 
    public StreamConnector(Stream s1, Stream s2) 
    { 
     StreamConnectorState state1 = new StreamConnectorState(s1, s2); 
     StreamConnectorState state2 = new StreamConnectorState(s2, s1); 
     s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1); 
     s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2); 
    } 

    private void ReadCallback(IAsyncResult result) 
    { 
     // Get state object. 
     StreamConnectorState state = (StreamConnectorState)result.AsyncState; 

     // Check to make sure Streams are still connected before processing. 
     if (state.InStream.IsConnected() && state.OutStream.IsConnected()) 
     { 
      // Finish reading data. 
      int length = state.InStream.EndRead(result); 

      // Write data. 
      state.OutStream.Write(state.Buffer, 0, length); 

      // Wait for new data. 
      state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state); 
     } 
     else 
     { 
      // Dispose of both streams if either of them is no longer connected. 
      state.InStream.Dispose(); 
      state.OutStream.Dispose(); 
     } 
    } 
} 

public class StreamConnectorState 
{ 
    private const int BYTE_ARRAY_SIZE = 4096; 

    public byte[] Buffer { get; set; } 
    public Stream InStream { get; set; } 
    public Stream OutStream { get; set; } 

    public StreamConnectorState(Stream inStream, Stream outStream) 
    { 
     Buffer = new byte[BYTE_ARRAY_SIZE]; 
     InStream = inStream; 
     OutStream = outStream; 
    } 
} 

public static class StreamExtensions 
{ 
    private static readonly byte[] POLLING_BYTE_ARRAY = new byte[0]; 

    public static bool IsConnected(this Stream stream) 
    { 
     try 
     { 
      // Twice because the first time will return without issue but 
      // cause the Stream to become closed (if the Stream is actually 
      // closed.) 
      stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length); 
      stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length); 
      return true; 
     } 
     catch (ObjectDisposedException) 
     { 
      // Since we're disposing of both Streams at the same time, one 
      // of the streams will be checked after it is disposed. 
      return false; 
     } 
     catch (IOException) 
     { 
      // This will be thrown on the second stream.Write when the Stream 
      // is closed on the client side. 
      return false; 
     } 
    } 
} 
+0

Por favor, no coloque títulos con 'C#', para eso son las etiquetas :) – kprobst

+0

Lo siento. :) Vi la mención del idioma en algunos otros títulos mientras investigaba; pensé que sería útil. Se pegará a las etiquetas desde aquí. – zimdanen

Respuesta

3

Tienes que intentar leer o escribir en un zócalo - o cualquier cosa basada en ella - para detectar una desconexión.

Intentar escribir arrojará una excepción/devolverá un error (según el paradigma de su idioma) o posiblemente solo escriba 0 bytes. Intentar leer arrojará una excepción/devolverá un error (nuevamente según el paradigma de su idioma) o devolverá null.

Vale la pena señalar que si está utilizando un modelo de servidor basado en selección, el socket desconectado aparece, es decir, devuelve seleccionar, como legible cuando se desconecta, luego intenta leer de él y obtener el error o null.

+0

Entonces, ¿debería intentar escribir en cada transmisión (lo que podría dar como resultado datos incorrectos en la transmisión si no está cerrado)? – zimdanen

+0

Adivina, si escribo 0 bytes, eso no debería ser un problema. – zimdanen

+2

Podría agregar que de acuerdo con [** Nito **] (http://nitoprograms.blogspot.co.il/2009/05/detection-of-half-open-dropped.html) la única forma de detectar una caída la conexión es escribir en la secuencia. –

0

No puedo evitar pensar que su cliente debería decirle al servidor cuando haya terminado con algún tipo de mensaje. Siempre es mejor estar preparado para cortar un cable o una falla de alimentación o un enchufe, pero en general, desea terminar una conexión con algún tipo de marcador de fin de mensaje. Guarde las excepciones para problemas reales, no para conversaciones normales.

+0

Al desechar el SslStream en el cliente (que posee el NetworkStream, que posee el Socket), no se envía nada al servidor para informarle que está desconectado. Al menos, nada que cierre el socket/stream en el servidor. – zimdanen

+0

@zimdanen: Veo eso como un (gran) problema de diseño con el SslStream. No hay mucho que hacer además de lo que has hecho si el SslStream está más allá de tu autoridad. Tenemos que hacer que nuestro software funcione con el mundo tal como es y no como debería ser. (No puedo resistir gruñir al respecto, sin embargo) – RalphChapin

Cuestiones relacionadas