2016-02-17 2 views
5

J'ai une classe TcpClient sur une configuration client et serveur sur ma machine locale. J'ai utilisé le flux de réseau pour faciliter les communications entre les 2 avec succès. Aller de l'avant J'essaie d'implémenter la compression dans les communications. J'ai essayé GZipStream et DeflateStream. J'ai décidé de me concentrer sur DeflateStream. Cependant, la connexion est suspendue sans lire les données maintenant.Pourquoi mon DeflateStream ne reçoit-il pas correctement les données via TCP?

J'ai essayé 4 implémentations différentes qui ont toutes échoué parce que le côté serveur ne lisait pas les données entrantes et que le délai de connexion était dépassé. Je vais me concentrer sur les deux implémentations que j'ai essayées le plus récemment et à ma connaissance devraient fonctionner.

Le client est décomposé à cette demande: Il existe 2 implémentations distinctes, une avec un streamwriter un sans.

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(); 
    //} 
} 

Le serveur reçoit la demande et je ne
1.) une lecture rapide du premier caractère alors si elle correspond à ce que j'attends
2.) Je continue à lire le reste.

La première lecture fonctionne correctement et si je veux lire le flux entier, tout est là. Cependant, je veux seulement lire le premier caractère et l'évaluer puis continuer dans ma méthode LongReadStream. Lorsque j'essaie de continuer à lire le flux, il n'y a pas de données à lire. Je devine que les données sont perdues pendant la première lecture mais je ne suis pas sûr de savoir comment déterminer cela. Tout ce code fonctionne correctement lorsque j'utilise le NetworkStream normal.

Voici le code côté serveur.

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 a une propriété Socket sous-jacente qui a une propriété disponible. MSDN dit ceci à propos de la propriété disponible.

Obtient la quantité de données qui a été reçue du réseau et est disponible pour être lu.

Avant l'appel ci-dessous est disponible 77. Après avoir lu 1 octet la valeur est 0.

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

Il ne semble pas y avoir de la documentation sur l'ensemble DeflateStream consommer flux sous-jacent et je n » Je ne sais pas pourquoi il ferait une telle chose quand il y a des appels explicites à faire pour lire des nombres spécifiques d'octets.

Est-ce que quelqu'un sait pourquoi cela se produit ou s'il y a un moyen de préserver les données sous-jacentes pour une lecture future? Basé sur cette 'fonctionnalité' et un précédent article that I read indiquant qu'un DeflateStream doit être fermé pour terminer l'envoi (flush ne fonctionnera pas) il semble que DeflateStreams peut être limité dans leur utilisation pour le réseautage surtout si l'on veut contrer les attaques DOS en testant les données avant accepter un flux complet.

+0

Juste spéculant, mais peut-être le constructeur de DeflateStream en QuickReadStream détecte que NetworkStream est un avant uniquement en lecture seule flux et lire toute la chose en zippy. Vous lisez ensuite le premier octet de zippy, définissez votre returnValue et revenez. Lorsque zippy est hors de portée, il n'y a rien à lire dans le NetworkStream car il a déjà été lu et désactivé. – Kevin

+0

J'ai mis à jour ma question en fonction de votre commentaire apparemment correct. Ceci est fondamentalement problématique comme indiqué dans ma question. –

+0

L'atténuation DOS est généralement effectuée au niveau de l'appliance réseau, où les données de flux peuvent être surveillées/analysées au fur et à mesure. Du point de vue de l'application, je ne suis pas certain que vous puissiez faire beaucoup pour détecter ou atténuer une telle attaque. Ma seule suggestion serait de lire le flux dans son intégralité dans un MemoryStream, de le tester, puis de le rejeter s'il ne l'est pas. Cela ne réduirait pas la charge de trafic TCP d'une attaque, mais pourrait empêcher le traitement/stockage inutile des données de la corbeille. – Kevin

Répondre

0

Fondamentalement, il y a quelques problèmes avec le code que j'ai posté ci-dessus. Tout d'abord, lorsque je lis des données, je ne fais rien pour m'assurer que les données sont TOUTES lues.Selon la documentation de Microsoft

L'opération de lecture se lit comme autant de données que sont disponibles, au nombre d'octets spécifié par le paramètre de taille.

Dans mon cas, je ne faisais pas en sorte que mes lectures puissent obtenir toutes les données que je m'attendais.

Ceci peut être accompli simplement avec ce code.

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

En plus de ce problème, j'ai eu un problème fondamental à l'utilisation DeflateStream - à savoir que je ne devrais pas utiliser DeflateStream d'écrire NetworkStream sous-jacent. L'approche correcte consiste à utiliser d'abord le DeflateStream pour compresser les données dans un ByteArray, puis envoyer ce ByteArray en utilisant directement le NetworkStream. L'utilisation de cette approche a permis de compresser correctement les données sur le réseau et la propriété a lu les données à l'autre extrémité.

Vous pouvez souligner que je dois connaître la taille des données, et c'est vrai. Chaque appel a un «en-tête» de 8 octets qui inclut la taille des données compressées et la taille des données lorsqu'il n'est pas compressé. Bien que je pense que le second n'était finalement pas nécessaire.

Le code pour ceci est ici. Notez que la variable compressedSize sert à 2 fins.

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

Avec cette information, je peux utiliser correctement le code que je vous ai montré en premier pour obtenir le contenu complet.

Une fois que je le tableau complet d'octets compressé que je peux obtenir les données entrantes comme ceci:

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

La faille de base que je peux penser à regarder votre code est une incompréhension possible du fonctionnement du flux réseau et de la compression.

Je pense que votre code pourrait fonctionner si vous continuiez à travailler avec un DeflateStream. Cependant, vous en utilisez un dans votre lecture rapide et vous en créez un autre.

Je vais essayer d'expliquer mon raisonnement sur un exemple. Supposons que vous ayez 8 octets de données d'origine à envoyer sur le réseau de manière compressée. Supposons maintenant, à titre d'argument, que chaque octet (8 bits) des données d'origine sera compressé en 6 bits sous forme compressée. Voyons maintenant ce que votre code fait pour cela. A partir du flux réseau, vous ne pouvez pas lire moins de 1 octet. Vous ne pouvez pas prendre 1 bit seulement. Vous prenez 1 octet, 2 octets, ou n'importe quel nombre d'octets, mais pas de bits. Mais si vous ne voulez recevoir que 1 octet des données d'origine, vous devez lire le premier octet entier des données compressées. Cependant, il n'y a que 6 bits de données compressées qui représentent le premier octet des données non compressées. Les 2 derniers bits du premier octet sont là pour le second octet des données d'origine.

Maintenant, si vous coupez le flux, il reste 5 octets dans le flux réseau qui n'ont aucun sens et ne peuvent pas être décompressés. L'algorithme de dégonflement est plus complexe que cela et il est donc parfaitement logique s'il ne vous permet pas d'arrêter de lire depuis le NetworkStream à un moment donné et de continuer avec le nouveau DeflateStream à partir du milieu. Il y a un contexte de la décompression qui doit être présent afin de décompresser les données dans leur forme originale. Une fois que vous disposez du premier DeflateStream dans votre lecture rapide, ce contexte est parti, vous ne pouvez pas continuer. Donc, pour résoudre votre problème, essayez de ne créer qu'un seul DeflateStream et passez-le à vos fonctions, puis jetez-le.

+0

Eh bien, un dégonflement unique pour la méthode entière a semblé aider, mais la séquence échoue encore avant que la conversation soit terminée. Je pense qu'il n'est pas possible d'utiliser DeflateStream avec une connexion tcpclient pour continuer à aller et venir. –

+0

Vous ne pouvez pas revenir dans ce flux, mais vous devriez pouvoir en lire une partie, puis lire le reste. Cela me surprendrait vraiment, si ce n'était pas possible et que vous auriez à le lire en une fois. – Wapac

+0

@AdamHeeg Oh, à droite, vous ne pouvez pas vider de manière fiable un flux de dégonflage, je pense que la sortie pourrait être à une position partielle octet. Vous avez probablement besoin de concevoir un format de trame de message dans lequel vous préfixez la longueur compressée en entier non compressé. Ensuite, envoyez le flux compressé compressé. Cela complique les choses bien sûr. Va ajouter ceci à ma réponse. – usr

2

Ceci est cassé de plusieurs façons.

  1. Vous supposez qu'un appel en lecture lira le nombre exact d'octets souhaité. Il pourrait cependant tout lire en morceaux d'un octet.
  2. DeflateStream possède un tampon interne. Cela ne peut pas être autrement: Les octets d'entrée ne correspondent pas 1: 1 aux octets de sortie. Il doit y avoir une mise en mémoire tampon interne. Vous devez utiliser un tel flux.
  3. Même problème avec UTF-8: les chaînes codées en UTF-8 ne peuvent pas être fractionnées aux limites d'octets. Parfois, vos données Unicode seront brouillées.
  4. Ne touchez pas ReceiveBufferSize, cela n'aide en rien.
  5. Vous ne pouvez pas vider de manière fiable un flux de dégonflement, je pense, car la sortie peut être à une position d'octet partiel. Vous devriez probablement concevoir un format de trame de message dans lequel vous préfixez la longueur compressée comme un entier non compressé. Ensuite, envoyez le flux compressé compressé après la longueur. Ceci est décodable de manière fiable.

La résolution de ces problèmes n'est pas facile.

Étant donné que vous semblez contrôler le client et le serveur, vous devez ignorer tout cela et ne pas concevoir votre propre protocole réseau. Utilisez un mécanisme de niveau supérieur tel que les services Web, HTTP, protobuf. Tout est meilleur que ce que vous avez là.

+0

J'apprécie vos commentaires. Nous ferons de notre mieux pour mettre en œuvre notre propre solution, en utilisant un mécanisme de niveau supérieur n'est pas préféré ici.Je vais essayer d'utiliser votre conseil à mon avantage, merci. Je suis d'accord, j'ai beaucoup à apprendre sur ce sujet. –