Lire la ligne d'un tableau d'octets (ne pas convertir un tableau d'octets en chaîne)
-
20-08-2019 - |
Question
J'ai un tableau d'octets que je lis depuis un NetworkStream. Les deux premiers octets indiquent la longueur du paquet qui suit, puis le paquet est lu dans un tableau d'octets de cette longueur. Les données dans lesquelles j'ai besoin de lire à partir du tableau NetworkStream / byte ont quelques chaînes, c'est-à-dire des données de longueur variable terminées par des caractères de nouvelle ligne et des champs de largeur fixe tels que bytes et longs. Donc, quelque chose comme ça:
// I would have delimited these for clarity but I didn't want
// to imply that the stream was delimited because it's not.
StringbyteStringStringbytebytebytelonglongbytelonglong
Je connais le format du paquet de données (et j'ai son mot à dire), et ce que je dois faire, c'est lire une & "ligne &"; pour chaque valeur de chaîne, mais lit un nombre d'octets fixe pour les octets et les longs. Jusqu'ici, ma solution proposée consiste à utiliser une boucle while
pour lire des octets dans un tableau d'octets temporaire jusqu'à l'apparition d'un caractère de nouvelle ligne. Ensuite, convertissez les octets en chaîne. Cela me semble ridicule, mais je ne vois pas d'autre moyen évident. Je réalise que je pourrais utiliser StreamReader.ReadLine()
mais cela impliquerait un autre flux et j’ai déjà un NetworkStream
. Mais si c'est la meilleure solution, je vais essayer.
L’autre option envisagée est de demander à mon équipe backend d’écrire un octet ou deux pour les longueurs de ces valeurs de chaîne afin que je puisse lire la longueur puis lire la chaîne en fonction de la longueur spécifiée.
Ainsi, comme vous pouvez le constater, j’ai quelques options sur la façon de procéder, et j'aimerais connaître votre avis sur ce que vous envisageriez comme le meilleur moyen de procéder. Voici le code que j'ai en ce moment pour lire l'intégralité du paquet sous forme de chaîne. La prochaine étape consiste à décomposer les différents champs du paquet et à effectuer le travail de programmation nécessaire, à savoir la création d’objets, la mise à jour de l’UI, etc. en fonction des données du paquet.
string line = null;
while (stream.DataAvailable)
{
//Get the packet length;
UInt16 packetLength = 0;
header = new byte[2];
stream.Read(header, 0, 2);
// Need to reverse the header array for BitConverter class if architecture is little endian.
if (BitConverter.IsLittleEndian)
Array.Reverse(header);
packetLength = BitConverter.ToUInt16(header,0);
buffer = new byte[packetLength];
stream.Read(buffer, 0, BitConverter.ToUInt16(header, 0));
line = System.Text.ASCIIEncoding.ASCII.GetString(buffer);
Console.WriteLine(line);
}
La solution
Personnellement je le ferais
- Mettez un Int16 au début de la cordes, donc vous savez combien de temps ils vont être, et
- Utilisez la classe IO.BinaryReader pour faire la lecture, il va & "lire &", ints, des chaînes, des caractères, etc. dans une variable, par ex. BinReader.ReadInt16 () lira deux octets, renverra l'int16 qu'ils représentent et déplacera deux octets dans le flux
J'espère que cela vous aidera.
P.S. Soyez prudent en utilisant la méthode ReadString, car elle suppose que la chaîne est complétée par des entiers personnalisés à 7 bits, c’est-à-dire qu’elle a été écrite par la classe BinaryWriter. Ce qui suit est extrait de cette CodeGuru post
La classe BinaryWriter a deux méthodes pour écrire des chaînes: le surchargé Méthode Write () et WriteString () méthode. Le premier écrit la chaîne comme un flux d'octets selon le l'encodage de la classe utilise. le WriteString () utilise également le codage spécifié, mais préfixes le flux d'octets de la chaîne avec le longueur réelle de la chaîne. Tel les chaînes préfixées sont relues via BinaryReader.ReadString ().
La chose intéressante à propos de la longueur valorisez le moins d'octets possible sont utilisés pour tenir cette taille, il est stocké sous un type appelé 7 bits nombre entier codé. Si la longueur convient 7 bits, un seul octet est utilisé, s'il est plus que cela alors le bit haut sur le premier octet est défini et un second octet est créé en décalant la valeur par 7 bits. Ceci est répété avec octets successifs jusqu'à ce qu'il y ait assez d'octets pour contenir la valeur. Ce mécanisme est utilisé pour s'assurer que la longueur ne devient pas un portion importante de la taille prise up par la chaîne sérialisée. BinaryWriter et BinaryReader ont méthodes pour lire et écrire 7 bits entiers codés, mais ils sont protégé et que vous ne pouvez les utiliser que si vous dérivez de ces classes.
Autres conseils
Je choisirais des chaînes avec un préfixe de longueur. Votre vie sera beaucoup plus simple, et cela signifie que vous pouvez représenter des chaînes avec des sauts de ligne. Quelques commentaires sur votre code cependant:
- N'utilisez pas Stream.DataAvailable. Le fait qu’aucune donnée ne soit disponible maintenant ne signifie pas que vous avez lu la fin du flux.
- Sauf si vous êtes absolument sûr de ne jamais avoir besoin de texte au-delà de ASCII, n'utilisez pas ASCIIEncoding.
- Ne supposez pas que Stream.Read lira toutes les données que vous lui demandez. Toujours vérifiez la valeur de retour.
- BinaryReader facilite beaucoup cela (y compris les chaînes avec un préfixe de longueur et une lecture qui boucle jusqu'à ce que vous lisiez ce que vous lui avez demandé)
- Vous appelez BitConverter.ToUInt16 deux fois sur les mêmes données. Pourquoi?