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;
}
}
}
Por favor, no coloque títulos con 'C#', para eso son las etiquetas :) – kprobst
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