2012-03-14 17 views
9

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; 
     } 
    } 
} 
+0

Si prega di non prefisso titoli con 'C#', questo è quello che i tag sono per :) – kprobst

+0

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

risposta

3

Bisogna tentare di leggere o scrivere ad una presa - o qualcosa basati su di esso - per rilevare una disconnessione.

Il tentativo di scrittura genera un'eccezione/restituisce un errore (a seconda del paradigma della lingua) o, eventualmente, solo la scrittura di 0 byte. Il tentativo di leggere genera un'eccezione/restituisce un errore (sempre in base al paradigma della lingua) o restituisce null.

Vale la pena notare che se si utilizza un modello di server basato su select, il socket disconnesso si presenta - cioè restituisce select - come leggibile quando si disconnette, quindi si tenta di leggere da esso e ottenere l'errore o null.

+0

Quindi dovrei tentare di scrivere su ogni stream (che potrebbe causare dati non validi sullo streaming se non è chiuso)? – zimdanen

+0

Indovina, se sto scrivendo 0 byte, non dovrebbe essere un problema. – zimdanen

+2

Posso aggiungere che in base a [** Nito **] (http://nitoprograms.blogspot.co.il/2009/05/detection-of-half-open-dropped.html) l'unico modo per rilevare una caduta la connessione è scrivere nel flusso. –

0

Non posso fare a meno di pensare che il tuo client dovrebbe dire al server quando ha finito con qualche tipo di messaggio. È sempre meglio essere preparati per un cavo interrotto o in mancanza di corrente o per il prelievo di una spina, ma in generale si desidera terminare una connessione con una sorta di indicatore di fine messaggio. Salva le eccezioni per problemi reali, non per normali conversazioni.

+0

Quando si smaltisce SslStream sul client (che possiede NetworkStream, che possiede il socket), non viene inviato nulla al server per informarlo che è disconnesso. Almeno, nulla che chiude il socket/stream sul server. – zimdanen

+0

@zimdanen: Lo vedo come un (grande) problema di progettazione con lo SslStream. Non c'è molto da fare oltre a quello che hai fatto se lo SslStream è al di fuori della tua autorità. Dobbiamo far funzionare il nostro software con il mondo così com'è e non come dovrebbe essere. (Non posso resistere a grugnire su di esso, però.) – RalphChapin

Problemi correlati