Question

J'ai rencontré ce que je pense être un problème avec la méthode BinaryReader.ReadChars (). Lorsque j'entoure un BinaryReader autour d'une socket brute NetworkStream, il arrive parfois que le flux en cours soit corrompu et que le flux en cours de lecture ne soit plus synchronisé. Le flux en question contient des messages dans un protocole de sérialisation binaire.

Je suis arrivé à ce qui suit

  • Cela ne se produit que lors de la lecture d'une chaîne unicode (codée à l'aide de Encoding.BigEndian)
  • Cela ne se produit que lorsque la chaîne en question est divisée en deux paquets TCP (confirmés à l'aide de Wireshark)

Je pense que ce qui se passe est le suivant (dans le contexte de l'exemple ci-dessous)

  • BinaryReader.ReadChars () est appelé pour lui demander de lire 3 caractères (les longueurs de chaîne sont codées avant la chaîne elle-même)
  • La première boucle demande en interne une lecture de 6 octets (3 caractères restants * 2 octets / caractère) hors du flux réseau
  • Le flux réseau ne dispose que de 3 octets disponibles
  • 3 octets lus dans le tampon local
  • Tampon remis au décodeur
  • Le décodeur décode 1 caractère et conserve l'autre octet dans son propre tampon interne
  • La deuxième boucle demande en interne une lecture de 4 octets! (2 caractères restants * 2 octets / caractère)
  • Les 4 octets disponibles dans le flux de réseau
  • 4 octets lus dans le tampon local
  • Tampon remis au décodeur
  • Le décodeur décode 2 caractères et conserve les 4ème octets restants en interne
  • Le décodage de la chaîne est terminé
  • Le code de sérialisation tente de disséminer l’élément suivant et provoque un croassement à cause de la corruption du flux.

    char[] buffer = new char[3];
    int charIndex = 0;
    
    Decoder decoder = Encoding.BigEndianUnicode.GetDecoder();
    
    // pretend 3 of the 6 bytes arrives in one packet
    byte[] b1 = new byte[] { 0, 83, 0 };
    int charsRead = decoder.GetChars(b1, 0, 3, buffer, charIndex);
    charIndex += charsRead;
    
    // pretend the remaining 3 bytes plus a final byte, for something unrelated,
    // arrive next
    byte[] b2 = new byte[] { 71, 0, 114, 3 };
    charsRead = decoder.GetChars(b2, 0, 4, buffer, charIndex);
    charIndex += charsRead;
    

Je pense que la racine est un bogue du code .NET qui utilise charsRemaining * octets / caractère à chaque boucle pour calculer les octets restants requis. En raison de l'octet supplémentaire caché dans le décodeur, ce calcul peut être désactivé, ce qui entraîne la consommation d'un octet supplémentaire hors du flux d'entrée.

Voici le code du framework .NET en question

    while (charsRemaining>0) { 
        // We really want to know what the minimum number of bytes per char 
        // is for our encoding.  Otherwise for UnicodeEncoding we'd have to
        // do ~1+log(n) reads to read n characters. 
        numBytes = charsRemaining;
        if (m_2BytesPerChar)
            numBytes <<= 1;

        numBytes = m_stream.Read(m_charBytes, 0, numBytes);
        if (numBytes==0) { 
            return (count - charsRemaining); 
        } 
        charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, buffer, index);

        charsRemaining -= charsRead;
        index+=charsRead;
    }

Je ne suis pas tout à fait sûr qu'il s'agisse d'un bogue ou simplement d'une mauvaise utilisation de l'API. Pour résoudre ce problème, je ne fais que calculer moi-même les octets nécessaires, en les lisant, puis en exécutant l'octet [] dans Encoding.GetString () approprié. Cependant, cela ne fonctionnerait pas pour quelque chose comme UTF-8.

Intéressez-vous à entendre les opinions des gens à ce sujet et à me demander si je fais quelque chose de mal ou non. Et peut-être que cela évitera à la personne suivante quelques heures / jours de débogage fastidieux.

EDIT: posté pour vous connecter Élément de suivi de connexion

Était-ce utile?

La solution

J'ai reproduit le problème que vous avez mentionné avec BinaryReader.ReadChars .

Bien que le développeur ait toujours besoin de prendre en compte l'apparence avant de composer des éléments tels que les flux et les décodeurs, ceci semble être un bogue assez important dans BinaryReader car cette classe est destinée à la lecture de structures de données composées de divers types de fichiers. Les données. Dans ce cas, je conviens que ReadChars aurait dû être plus prudent dans sa lecture pour éviter de perdre cet octet.

Votre solution de contournement consistant à utiliser Decoder directement n’est pas un problème, après tout, c’est ce que ReadChars fait en coulisse.

Unicode est un cas simple. Si vous envisagez un codage arbitraire, il n'existe aucun moyen générique de garantir que le nombre d'octets correct est utilisé lorsque vous transmettez un nombre de caractères au lieu d'un nombre d'octets (pensez aux caractères de longueur variable et aux cas impliquant une entrée mal formée). Pour cette raison, éviter BinaryReader.ReadChars au lieu de lire le nombre d'octets spécifique fournit une solution plus robuste et générale.

Je vous suggère de le signaler à Microsoft via http://connect.microsoft.com/visualstudio .

Autres conseils

Intéressant; vous pouvez le signaler sur "Connecter". En guise de solution de rechange, vous pouvez également essayer d'encapsuler avec BufferredStream , mais je suppose que cela recouvre une fissure (cela peut arriver, mais moins fréquemment).

L’autre approche consiste bien entendu à pré-tamponner un message entier (mais pas le flux entier); lisez ensuite quelque chose comme MemoryStream - en supposant que votre protocole réseau contienne des messages logiques (et idéalement préfixés en longueur et pas trop gros). Puis, quand il est décodé , toutes les données sont disponibles.

Cela rappelle l'une de mes propres questions ( la lecture à partir d'un HttpResponseStream échoue ). où j'avais un problème qui, lors de la lecture d'un flux de réponse HTTP, laissait penser à StreamReader qu'il avait atteint la fin du flux prématurément, de sorte que mes analyseurs syntaxiques bombardaient inopinément.

Comme Marc l’a suggéré pour votre problème, j’ai tout d’abord essayé la pré-mise en mémoire tampon dans un MemoryStream qui fonctionne bien, mais signifie que vous devrez peut-être attendre longtemps si vous avez un fichier volumineux à lire (surtout à partir du fichier. réseau / Web) avant de pouvoir faire quoi que ce soit d’utile avec. J'ai finalement décidé de créer ma propre extension de TextReader qui remplace les méthodes de lecture et les définit à l'aide de la méthode ReadBlock (qui effectue une lecture bloquante, c'est-à-dire qu'elle attend jusqu'à ce qu'elle puisse obtenir exactement le nombre de caractères que vous demandez).

Votre problème est probablement dû, comme le mien, au fait que les méthodes de lecture ne sont pas garanties pour renvoyer le nombre de caractères que vous demandez, par exemple si vous consultez la documentation de BinaryReader.Read ( http://msdn.microsoft.com/en-us/library/ms143295 .aspx ), vous verrez qu'il est écrit:

  

Valeur de retour
  Type: System .. ::. Int32
  Le nombre de caractères lus dans le tampon. Ce nombre peut être inférieur au nombre d'octets demandés si ce nombre n'est pas disponible ou égal à zéro si la fin du flux est atteinte.

Etant donné que BinaryReader n’a pas de méthode ReadBlock comme un TextReader, vous ne pouvez que suivre votre propre approche en surveillant vous-même ou Marc la pré-mise en cache.

Je travaille avec Unity3D / Mono atm et la méthode ReadChars pourrait même contenir plus d'erreurs. J'ai fait une chaîne comme ceci:

mat.name = new string(binaryReader.ReadChars(64));

mat.name contenait même la chaîne correcte, mais je pouvais simplement ajouter des chaînes avant . Tout après la chaîne a disparu. Même avec String.Format. Ma solution jusqu'à présent n'utilise pas la méthode ReadChars, mais lit les données sous forme de tableau d'octets et les convertit en chaîne:

byte[] str = binaryReader.ReadBytes(64);
int lengthOfStr = Array.IndexOf(str, (byte)0); // e.g. 4 for "clip\0"
mat.name = System.Text.ASCIIEncoding.Default.GetString(str, 0, lengthOfStr);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top