Una breve sinossi della situazione:È possibile rilevare se un flusso è stato chiuso dal client?
Ho un servizio che prende le informazioni in e invia risposte fuori sopra Sockets. Le connessioni non sono protette. Voglio configurare un altro servizio in grado di fornire TLS a queste connessioni: questo nuovo servizio fornirebbe una singola porta e distribuirà le connessioni in base al certificato client fornito. Non voglio usare stunnel per un paio di ragioni, una delle quali è che richiederebbe una porta di inoltro per porta di ricezione.
La soluzione Attualmente sto cercando di attuare:
In sostanza, sto cercando di accoppiare uno SslStream (in entrata) con una NetworkStream (in uscita - potrebbe essere un socket, ma ho messo in un NetworkStream per abbinare l'entrata) e avere le operazioni di lettura/scrittura collegate per i due. Questo collegamento fornirebbe il flusso tra il client (su SSL/TLS) e il servizio (su una connessione non protetta).
Ecco la classe mi si avvicinò con per collegare questi flussi:
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;
}
}
Il problema:
Quando il client è fatto l'invio di informazioni e dispone della SslStream, il server non lo fa avere una sorta di indicazione se questo è successo o meno. Questa classe StreamConnector continua felicemente a correre nell'eternità senza generare alcun tipo di errore e non riesco a trovare alcun indicatore che dovrebbe fermarsi. (C'è, ovviamente, il fatto che ottengo 0 lunghezza ogni volta in ReadCallback, ma devo essere in grado di fornire connessioni di lunga durata, quindi questo non è un buon modo per giudicare.)
Un altro potenziale il problema è che il ReadCallback viene chiamato anche se non ci sono dati disponibili. Non sono sicuro se sarebbe diverso se stessimo usando un socket direttamente invece di uno stream, ma sembra inefficiente continuare a far girare quel codice all'infinito.
Le mie domande:
1) C'è un modo per dire se un flusso è stato chiuso dal lato client?
2) Esiste un modo migliore per fare ciò che sto cercando di fare?
2a) Esiste un modo più efficiente per eseguire il ciclo di lettura/scrittura asincrono?
MODIFICA: Grazie, Robert. Risulta che il loop continuava a essere chiamato perché non stavo chiudendo gli Stream (perché non sapevo come dire quando gli Stream dovevano essere chiusi). Sto compresa la soluzione codice completo nel caso in cui qualcun altro corre in questo 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;
}
}
}
Si prega di non prefisso titoli con 'C#', questo è quello che i tag sono per :) – kprobst
Siamo spiacenti. :) Ho visto la menzione della lingua in altri titoli mentre stavo ricercando; pensato che sarebbe stato utile. Attaccherà ai tag da qui fuori. – zimdanen