2016-02-17 7 views
5

Ho una classe TcpClient su un'installazione client e server sul mio computer locale. Ho utilizzato il flusso di rete per facilitare le comunicazioni avanti e indietro tra i 2 con successo.Perché il mio DeflateStream non riceve correttamente i dati tramite TCP?

In avanti Sto tentando di implementare la compressione nelle comunicazioni. Ho provato GZipStream e DeflateStream. Ho deciso di concentrarmi su DeflateStream. Tuttavia, la connessione si blocca senza leggere i dati ora.

Ho provato 4 diverse implementazioni che hanno tutte fallito a causa del lato server che non leggeva i dati in entrata e il timeout della connessione. Mi concentrerò sulle due implementazioni che ho provato più di recente e, a mia conoscenza, dovrebbe funzionare.

Il client è suddiviso in base a questa richiesta: Esistono 2 implementazioni separate, una con streamwriter una senza.

textToSend = ENQUIRY + START_OF_TEXT + textToSend + END_OF_TEXT; 

// Send XML Request 
byte[] request = Encoding.UTF8.GetBytes(textToSend); 

using (DeflateStream streamOut = new DeflateStream(netStream, CompressionMode.Compress, true)) 
{ 
    //using (StreamWriter sw = new StreamWriter(streamOut)) 
    //{ 
    // sw.Write(textToSend); 
    // sw.Flush(); 
    streamOut.Write(request, 0, request.Length); 
    streamOut.Flush(); 
    //} 
} 

Il server riceve la richiesta e lo faccio
1.) una rapida lettura del primo carattere, quindi se corrisponde a quello che mi aspetto
2.) Io continuo a leggere il resto.

La prima lettura funziona correttamente e se voglio leggere l'intero flusso è tutto lì. Tuttavia, desidero solo leggere il primo carattere e valutarlo, quindi continuare nel mio metodo LongReadStream.

Quando provo a continuare a leggere lo stream non ci sono dati da leggere. Immagino che i dati vengano persi durante la prima lettura, ma non sono sicuro di come determinarlo. Tutto questo codice funziona correttamente quando utilizzo il normale NetworkStream.

Ecco il codice lato server.

private void ProcessRequests() 
{ 
    // This method reads the first byte of data correctly and if I want to 
    // I can read the entire request here. However, I want to leave 
    // all that data until I want it below in my LongReadStream method. 
    if (QuickReadStream(_netStream, receiveBuffer, 1) != ENQUIRY) 
    { 
     // Invalid Request, close connection 
     clientIsFinished = true; 
     _client.Client.Disconnect(true); 
     _client.Close(); 
     return; 
    } 



    while (!clientIsFinished) // Keep reading text until client sends END_TRANSMISSION 
    { 
     // Inside this method there is no data and the connection times out waiting for data 
     receiveText = LongReadStream(_netStream, _client); 

     // Continue talking with Client... 
    } 
    _client.Client.Shutdown(SocketShutdown.Both); 
    _client.Client.Disconnect(true); 
    _client.Close(); 
} 


private string LongReadStream(NetworkStream stream, TcpClient c) 
{ 
    bool foundEOT = false; 
    StringBuilder sbFullText = new StringBuilder(); 
    int readLength, totalBytesRead = 0; 
    string currentReadText; 
    c.ReceiveBufferSize = DEFAULT_BUFFERSIZE * 100; 

    byte[] bigReadBuffer = new byte[c.ReceiveBufferSize]; 

    while (!foundEOT) 
    { 
     using (var decompressStream = new DeflateStream(stream, CompressionMode.Decompress, true)) 
     { 
      //using (StreamReader sr = new StreamReader(decompressStream)) 
      //{ 
       //currentReadText = sr.ReadToEnd(); 
      //} 
      readLength = decompressStream.Read(bigReadBuffer, 0, c.ReceiveBufferSize); 
      currentReadText = Encoding.UTF8.GetString(bigReadBuffer, 0, readLength); 
      totalBytesRead += readLength; 
     } 

     sbFullText.Append(currentReadText); 

     if (currentReadText.EndsWith(END_OF_TEXT)) 
     { 
      foundEOT = true; 
      sbFullText.Length = sbFullText.Length - 1; 
     } 
     else 
     { 
      sbFullText.Append(currentReadText); 
     } 

     // Validate data code removed for simplicity 


    } 
    c.ReceiveBufferSize = DEFAULT_BUFFERSIZE; 
    c.ReceiveTimeout = timeOutMilliseconds; 
    return sbFullText.ToString(); 

} 



private string QuickReadStream(NetworkStream stream, byte[] receiveBuffer, int receiveBufferSize) 
{ 
    using (DeflateStream zippy = new DeflateStream(stream, CompressionMode.Decompress, true)) 
    { 
     int bytesIn = zippy.Read(receiveBuffer, 0, receiveBufferSize); 
     var returnValue = Encoding.UTF8.GetString(receiveBuffer, 0, bytesIn); 
     return returnValue; 
    } 
} 

EDIT NetworkStream ha una proprietà Presa di fondo che ha una proprietà disponibile. MSDN dice questo sulla proprietà disponibile.

Ottiene la quantità di dati che è stata ricevuta dalla rete ed è disponibile per la lettura.

Prima della chiamata di seguito disponibile è 77. Dopo aver letto 1 byte il valore è 0.

//receiveBufferSize = 1 
int bytesIn = zippy.Read(receiveBuffer, 0, receiveBufferSize); 

Non sembra esserci alcuna documentazione su DeflateStream consumando l'intero flusso sottostante e io don' so perché farebbe una cosa del genere quando ci sono chiamate esplicite da fare per leggere numeri specifici di byte.

Qualcuno sa perché questo succede o se c'è un modo per preservare i dati sottostanti per una lettura futura? Sulla base di questa 'caratteristica' e di una precedente affermando che un DeflateStream deve essere chiuso per terminare l'invio (flush non funzionerà) sembra che DeflateStreams possa essere limitato nel loro uso per la rete specialmente se si vuole contrastare gli attacchi DOS testando i dati in arrivo prima accettare un flusso completo.

+0

Appena speculare, ma forse il costruttore di DeflateStream in QuickReadStream rileva che NetworkStream è un inoltro solo lettura sola flusso e la lettura dell'intera cosa in zippy. Quindi leggi il primo byte da zippy, imposta il tuo returnValue e ritorna. Quando zippy esce dal campo di applicazione non c'è nulla da leggere in NetworkStream perché è già stato letto e disgustato. – Kevin

+0

Ho aggiornato la mia domanda sulla base del tuo commento apparentemente corretto. Questo è fondamentalmente problematico come notato nella mia domanda. –

+0

La mitigazione del DOS viene in genere eseguita a livello di appliance di rete, in cui i dati del flusso possono essere monitorati/analizzati al loro interno. Dal punto di vista dell'applicazione, non sono sicuro che ci sia molto da fare per rilevare o mitigare un simile attacco. Il mio unico suggerimento sarebbe quello di leggere il flusso nella sua interezza in un MemoryStream, testarlo, quindi scartare se non è giusto. Ciò non ridurrebbe il carico di traffico TCP di un attacco, ma potrebbe impedire l'elaborazione/archiviazione non necessaria dei dati sui rifiuti. – Kevin

risposta

0

Fondamentalmente ci sono alcune cose che non vanno nel codice che ho postato sopra. Il primo è che quando leggo i dati non sto facendo nulla per assicurarmi che i dati siano TUTTI letti.Come da documentazione di Microsoft

l'operazione di lettura si legge tutti i dati che è disponibile, fino al numero di byte specificato dal parametro di dimensione.

Nel mio caso non mi sono assicurato che le mie letture avrebbero ottenuto tutti i dati che mi aspettavo.

Questo può essere realizzato semplicemente con questo codice.

byte[] data= new byte[packageSize]; 
    bytesRead = _netStream.Read(data, 0, packageSize); 
    while (bytesRead < packageSize) 
     bytesRead += _netStream.Read(data, bytesRead, packageSize - bytesRead); 

Oltre a questo problema che aveva un problema fondamentale con l'utilizzo DeflateStream - cioè non dovrebbe utilizzare DeflateStream scrivere sul NetworkStream sottostante. L'approccio corretto consiste nel primo utilizzare DeflateStream per comprimere i dati in un ByteArray, quindi inviare direttamente ByteArray utilizzando NetworkStream.

L'utilizzo di questo approccio ha contribuito a comprimere correttamente i dati sulla rete e la proprietà legge i dati sull'altra estremità.

Si può notare che devo conoscere la dimensione dei dati, e questo è vero. Ogni chiamata ha un'intestazione di 8 byte che include la dimensione dei dati compressi e la dimensione dei dati quando non è compressa. Anche se penso che il secondo non sia stato assolutamente necessario.

Il codice per questo è qui. Nota la variabile compressedSize serve a 2 scopi.

int packageSize = streamIn.Read(sizeOfDataInBytes, 0, 4); 
while (packageSize!= 4) 
{ 
    packageSize+= streamIn.Read(sizeOfDataInBytes, packageSize, 4 - packageSize); 
} 
packageSize= BitConverter.ToInt32(sizeOfDataInBytes, 0); 

Con queste informazioni posso utilizzare correttamente il codice che ho mostrato prima di ottenere il contenuto completamente.

Dopo aver il pieno byte compresso posso ottenere come i dati in arrivo così:

var output = new MemoryStream(); 
using (var stream = new MemoryStream(bufferIn)) 
{ 
    using (var decompress = new DeflateStream(stream, CompressionMode.Decompress)) 
    { 
     decompress.CopyTo(output);; 
    } 
} 
output.Position = 0; 
var unCompressedArray = output.ToArray(); 
output.Close(); 
output.Dispose(); 
return Encoding.UTF8.GetString(unCompressedArray); 
2

Il difetto di base che posso pensare di guardare il codice è un possibile fraintendimento di come funzionano il flusso e la compressione della rete.

Penso che il tuo codice potrebbe funzionare, se continuassi a lavorare con uno DeflateStream. Tuttavia, ne usi uno nella tua lettura veloce e poi ne crei un altro.

Proverò a spiegare il mio ragionamento su un esempio. Supponiamo di avere 8 byte di dati originali da inviare in rete in modo compresso. Ora assumiamo per un argomento, che ogni singolo byte (8 bit) di dati originali verrà compresso a 6 bit in forma compressa. Ora vediamo cosa fa il tuo codice per questo.

Dal flusso di rete, non è possibile leggere meno di 1 byte. Non puoi prendere solo 1 bit. Prendi 1 byte, 2 byte o un numero qualsiasi di byte, ma non bit.

Ma se si desidera ricevere solo 1 byte dei dati originali, è necessario leggere il primo intero byte di dati compressi. Tuttavia, vi sono solo 6 bit di dati compressi che rappresentano il primo byte di dati non compressi. Gli ultimi 2 bit del primo byte sono lì per il secondo byte di dati originali.

Ora se si taglia lo stream, ciò che resta sono 5 byte nello stream di rete che non hanno alcun senso e non possono essere decompressi.

L'algoritmo di svuotamento è più complesso di quello e quindi ha perfettamente senso se non consente di interrompere la lettura da NetworkStream in un punto e continuare con il nuovo DeflateStream dal centro. Esiste un contesto di decompressione che deve essere presente per decomprimere i dati nella loro forma originale. Una volta che hai eliminato il primo DeflateStream nella tua lettura veloce, questo contesto è sparito, non puoi continuare.

Quindi, per risolvere il problema, provare a creare solo un DeflateStream e passarlo alle funzioni, quindi smaltirlo.

+0

Beh, un singolo svuotamento per l'intero metodo sembrava essere d'aiuto, ma la sequenza continua a fallire prima che la conversazione sia completa. Sto pensando che non è possibile utilizzare DeflateStream con una connessione TCP/IP per continue comunicazioni avanti e indietro. –

+0

Non è possibile tornare indietro in quel flusso, ma dovresti essere in grado di leggerne una parte e leggerne il resto. Mi sorprenderebbe davvero, se questo non fosse possibile e avresti dovuto leggerlo tutto in una volta. – Wapac

+0

@AdamHeeg oh, giusto, non è possibile eseguire il flush in modo affidabile di un flusso di deflate, perché l'output potrebbe trovarsi in una posizione di byte parziale. Probabilmente hai bisogno di escogitare un formato di framing dei messaggi dove anteponi la lunghezza compressa come un numero intero non compresso. Quindi, invia il flusso di deflate compresso. Ciò complica ulteriormente le cose naturalmente. Aggiungerò questo alla mia risposta. – usr

2

Questo è rotto in molti modi.

  1. Si presume che una chiamata in lettura leggerà il numero esatto di byte che si desidera. Potrebbe leggere tutto in blocchi di un byte però.
  2. DeflateStream ha un buffer interno. Non può essere diversamente: i byte di input non corrispondono 1: 1 ai byte in uscita. Ci deve essere un buffering interno. È necessario utilizzare uno di questi flussi.
  3. Stesso problema con UTF-8: le stringhe con codifica UTF-8 non possono essere divise ai limiti dei byte. A volte, i dati Unicode saranno confusi.
  4. Non toccare ReceiveBufferSize, non aiuta in alcun modo.
  5. Non è possibile eseguire il flush in modo affidabile di un flusso di deflate, perché l'output potrebbe trovarsi in una posizione di byte parziale. Probabilmente dovresti escogitare un formato di frame dei messaggi in cui anteponi la lunghezza compressa come un numero intero non compresso. Quindi, inviare il flusso di deflate compresso dopo la lunghezza. Questo è decodificabile in modo affidabile.

La risoluzione di questi problemi non è facile.

Dal momento che sembra controllare client e server, è necessario scartare tutto questo e non elaborare il proprio protocollo di rete. Utilizzare un meccanismo di livello superiore come servizi Web, HTTP, protobuf. Qualunque cosa è migliore di quella che hai lì.

+0

Apprezzo i tuoi commenti. Faremo del nostro meglio per implementare la nostra soluzione, utilizzando un meccanismo di livello superiore non è preferito qui.Proverò a usare il tuo consiglio a mio vantaggio, grazie. Sono d'accordo che ho molto da imparare su questo argomento. –

Problemi correlati