7

我有一个从 NetworkStream 读取的字节数组。前两个字节表示随后的数据包的长度,然后将数据包读入该长度的字节数组中。我需要从 NetworkStream/byte 数组中读取的数据有一些字符串,即由换行符终止的可变长度数据,以及一些固定宽度的字段,如字节和长整数。所以,像这样:

// I would have delimited these for clarity but I didn't want
// to imply that the stream was delimited because it's not.
StringbyteStringStringbytebytebytelonglongbytelonglong

我知道(并且有一些发言权)正在遇到的数据包的格式,我需要做的是为每个字符串值读取一个“行”,但为字节和 long 读取固定数量的字节。到目前为止,我提出的解决方案是使用while循环将字节读入临时字节数组,直到出现换行符。然后,将字节转换为字符串。这对我来说似乎很笨拙,但我没有看到另一种明显的方式。我意识到我可以使用StreamReader.ReadLine(),但这将涉及另一个流,而且我已经有一个NetworkStream. 但如果这是更好的解决方案,我会试一试。

我考虑的另一个选择是让我的后端团队为这些字符串值的长度写入一个或两个字节,以便我可以读取长度,然后根据指定的长度读取字符串。

所以,正如你所见,我有一些关于如何去做的选择,我希望你能就你认为最好的方法提供意见。这是我现在拥有的用于将整个数据包作为字符串读取的代码。接下来就是将数据包的各个字段打出来,根据数据包中的数据做实际需要做的编程工作,创建对象,更新UI等。

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);
}
4

2 回答 2

9

我个人会

  1. 将 Int16 放在字符串的开头,这样您就知道它们会持续多长时间,并且
  2. 使用 IO.BinaryReader 类进行读取,它会将整数、字符串、字符等“读取”到变量中,例如 BinReader.ReadInt16() 将读取两个字节,返回它们代表的 int16,然后在溪流

希望这可以帮助。

PS 使用 ReadString 方法时要小心,它假定字符串前面带有自定义的 7 位整数,即它是由 BinaryWriter 类编写的。以下来自此CodeGuru帖子

BinaryWriter 类有两种写入字符串的方法:重载的 Write() 方法和 WriteString() 方法。前者根据类使用的编码将字符串写入字节流。WriteString() 方法也使用指定的编码,但它在字符串的字节流前面加上字符串的实际长度。这样的前缀字符串通过 BinaryReader.ReadString() 读回。

关于长度值的有趣之处在于,它使用尽可能少的字节来保存这个大小,它被存储为一种称为 7 位编码整数的类型。如果长度适合 7 位,则使用单个字节,如果大于此长度,则设置第一个字节的高位,并通过将值移位 7 位来创建第二个字节。这会以连续的字节重复,直到有足够的字节来保存该值。此机制用于确保长度不会成为序列化字符串占用的大小的重要部分。BinaryWriter 和 BinaryReader 具有读取和写入 7 位编码整数的方法,但它们是受保护的,因此只有从这些类派生时才能使用它们。

于 2009-01-29T16:54:44.450 回答
5

我会使用以长度为前缀的字符串。它会让你的生活变得更简单,这意味着你可以用换行符来表示字符串。不过,对你的代码有一些评论:

  • 不要使用 Stream.DataAvailable。仅仅因为现在没有可用数据并不意味着您已经阅读了流的结尾。
  • 除非您绝对确定您永远不需要 ASCII 以外的文本,否则不要使用 ASCIIEncoding。
  • 不要假设 Stream.Read 会读取您要求它读取的所有数据。始终检查返回值。
  • BinaryReader 使这一切变得容易得多(包括以长度为前缀的字符串和一个 Read 循环,直到它读取你所要求的内容)
  • 您在同一数据上调用 BitConverter.ToUInt16 两次。为什么?
于 2009-01-29T17:11:58.440 回答