7

我遇到了我认为 BinaryReader.ReadChars() 方法的问题。当我在原始套接字 NetworkStream 周围包装 BinaryReader 时,偶尔会出现流损坏,其中正在读取的流不同步。有问题的流包含二进制序列化协议中的消息。

我已经追踪到以下内容

  • 它仅在读取 unicode 字符串时发生(使用 Encoding.BigEndian 编码)
  • 仅当有问题的字符串拆分为两个 tcp 数据包时才会发生(使用 wireshark 确认)

我认为正在发生的事情如下(在下面的例子中)

  • 调用 BinaryReader.ReadChars() 要求它读取 3 个字符(字符串长度在字符串本身之前编码)
  • 第一个循环在内部请求从网络流中读取 6 个字节(剩余 3 个字符 * 2 个字节/字符)
  • 网络流只有 3 个字节可用
  • 3 个字节读入本地缓冲区
  • 缓冲区交给解码器
  • 解码器解码 1 个字符,并将另一个字节保存在它自己的内部缓冲区中
  • 第二个循环内部请求读取 4 个字节!(2 个剩余字符 * 2 个字节/字符)
  • 网络流有全部 4 个字节可用
  • 4字节读入本地缓冲区
  • 缓冲区交给解码器
  • 解码器解码 2 个字符,并在内部保留剩余的第 4 个字节
  • 字符串解码完成
  • 由于流损坏,序列化代码尝试解组下一个项目并发出呱呱叫声。

    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;
    

我认为根是 .NET 代码中的一个错误,它使用 charsRemaining * bytes/char 每个循环来计算所需的剩余字节。由于解码器中隐藏了额外的字节,这个计算可能会被关闭,导致额外的字节从输入流中消耗掉。

这是有问题的 .NET 框架代码

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

我不完全确定这是一个错误还是只是滥用 API。为了解决这个问题,我只是自己计算所需的字节,读取它们,然后通过相关的 Encoding.GetString() 运行 byte[]。但是,这不适用于 UTF-8 之类的东西。

有兴趣听听人们对此的想法以及我是否做错了什么。也许它会为下一个人节省几个小时/几天的繁琐调试。

编辑:张贴连接连接跟踪项目

4

4 回答 4

3

我已经复制了你提到的问题BinaryReader.ReadChars

尽管开发人员在组合流和解码器之类的东西时总是需要考虑前瞻,但这似乎是一个相当重要的错误,BinaryReader因为该类旨在读取由各种类型的数据组成的数据结构。在这种情况下,我同意ReadChars应该在读取内容时更加保守,以避免丢失该字节。

直接使用的解决方法没有任何问题Decoder,毕竟这是ReadChars在幕后做的。

Unicode 是一个简单的例子。如果您考虑任意编码,那么当您传入字符计数而不是字节计数时,确实没有通用的方法来确保消耗正确的字节数(考虑不同长度的字符和涉及格式错误的输入的情况)。出于这个原因,避免BinaryReader.ReadChars读取特定数量的字节提供了一种更健壮、更通用的解决方案。

我建议您通过http://connect.microsoft.com/visualstudio让 Microsoft 注意到这一点。

于 2009-11-26T16:36:12.293 回答
1

有趣的; 你可以在“连接”上报告这个。作为权宜之计,您也可以尝试用 包裹BufferredStream,但我希望这是在裂缝上覆盖(它可能仍然会发生,但频率较低)。

当然,另一种方法是预缓冲整个消息(但不是整个流);然后从类似的东西中读取MemoryStream- 假设您的网络协议具有逻辑(理想情况下是长度前缀,而不是太大)消息。然后,当它解码时,所有数据都可用。

于 2009-11-26T16:34:05.247 回答
1

这让我想起了我自己的一个问题(从 HttpResponseStream 读取失败),我遇到了一个问题,当从 HTTP 响应流中读取时,StreamReader 会认为它过早地到达了流的末尾,所以我的解析器会意外地崩溃。

就像 Marc 为您的问题所建议的那样,我首先尝试在 a 中进行预缓冲MemoryStream,效果很好,但这意味着如果您要读取大文件(尤其是来自网络/网络),您可能需要等待很长时间才能执行任何有用的操作它。我最终决定创建我自己的 TextReader 扩展,它覆盖 Read 方法并使用 ReadBlock 方法定义它们(它执行阻塞读取,即它等待直到它可以准确地获得您要求的字符数)

您的问题可能和我一样是由于 Read 方法不能保证返回您要求的字符数,例如,如果您查看BinaryReader.Read( http://msdn.microsoft.com/en- ) 的文档us/library/ms143295.aspx ) 方法,您会看到它声明:

返回值
类型:System..::.Int32
读入缓冲区的字符数。如果有那么多字节不可用,这可能小于请求的字节数,或者如果到达流的末尾,它可能为零。

由于 BinaryReader 没有像 TextReader 这样的 ReadBlock 方法,您所能做的就是采用自己的方法来监控自己的位置或 Marc 的预缓存。

于 2009-11-26T17:32:22.850 回答
0

我正在使用 Unity3D/Mono atm,ReadChars 方法甚至可能包含更多错误。我做了一个这样的字符串:

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

mat.name甚至包含正确的字符串,但我可以在它之前添加字符串。字符串之后的一切都消失了。即使使用 String.Format。到目前为止,我的解决方案不是使用 ReadChars 方法,而是将数据读取为字节数组并将其转换为字符串:

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);
于 2014-08-17T23:51:24.320 回答