【问题标题】:Mixing StreamReader.Read... and Stream.Read混合 StreamReader.Read... 和 Stream.Read
【发布时间】:2014-05-13 01:05:33
【问题描述】:

我有一个用于 RTSP 通信的 TCP 套接字。 由于数据是基于行的文本数据和字节大小的块的混合,我将StreamReader 附加到tcpClient.GetStream(),并在需要文本数据时调用.ReadLine()

当我需要读取响应正文时,我有一个固定的字节数,所以我尝试使用 stream.Read() 但这会阻塞,大概是因为 StreamReader 已经将数据读入了它自己的缓冲区。与字符编码一样,它只读取固定数量的字符而不是字节。

有什么方法可以从流中读取固定数量的字节,而无需完全废弃 StreamReader 或希望二进制数据/内容是 7 位安全的(反过来不会解码为 UTF-8 )? 另一种方法是将StreamReader 的编码设置为ASCII,但这可能会破坏定义为UTF-8 的协议的其余部分。

设置:

this.rtspStream = this.rtspSocket.GetStream();
this.rtspReader = new StreamReader(this.rtspStream, Encoding.UTF8);

文字阅读:

string line;
while ((line = this.rtspReader.ReadLine()) != string.Empty) {
    // ...
}

二进制读取:

byte[] responseBody = new byte[contentLength];
this.rtspStream.Read(responseBody, 0, contentLength);

【问题讨论】:

  • 你能举一个RTSP格式数据的例子吗?令我惊讶的是,您必须逐行阅读,而不是有一个整数字段来指示文本数据的大小。如果是这种情况,那么我建议使用 Stream 阅读所有内容,然后当你想将文本块分成几行时,要么只使用 String.Split,要么在文本块上创建一个 MemoryStream,然后使用StreamReader 将其解析为行。
  • 它基于HTTP,因此单个请求行和多个分隔符,双新行终止。如果有内容(在大多数情况下只是 UTF-8 SDP 数据),则根据 HTTP 在其中一个标头中有明确的字节长度。 ietf.org/rfc/rfc2326.txt
  • 好的,那么,再一次,你能展示一下你想要做什么吗?如果都是行,那么我不明白您为什么还要读取字节。我认为它可能是二进制和文本的组合。请给出一个示意图。
  • 对不起,我在评论更新中,内容是固定的字节长度。虽然在大多数情况下它也是文本 SDP,但不能保证。是的,对于 SDP,我可以阅读行直到我认为内容的结尾,我宁愿只阅读“内容”。
  • StreamReader 的缓冲区大小设置为 1 不起作用,因为它有一个 internal minimum of 128 bytes

标签: .net sockets stream streamreader


【解决方案1】:

查看StreamReader 源,不可能混合使用StreamReader 和直接从流中读取字节,因为StreamReader 将字节读入其内部缓冲区,然后将它可以解码的所有内容解码为字符缓冲区可供读取。

将缓冲区大小设置为 0 无济于事,因为它强制将其设置为至少 128 字节。

为了我的使用,我需要完全废弃 StreamReader 并将 ReadLine() 替换为可以直接从流中读取以解析自己的内容。

private string ReadLine() {
    // Stringbuilder to insert the read characters in to
    StringBuilder line = new StringBuilder();

    // Create an array to store the maximum number of bytes for a single character
    byte[] bytes = new byte[this.encoding.GetMaxByteCount(1)];
    int byteCount = 0;

    while (true) {
        // Read the first byte
        bytes[0] = (byte)this.rtspStream.ReadByte();
        byteCount = 1;

        // If the encoding says this isn't a full character, read until it does
        while (this.encoding.GetCharCount(bytes, 0, byteCount) == 0) {
            bytes[byteCount++] = (byte)this.rtspStream.ReadByte();
        }

        // Get the unencoded character
        char thisChar = this.encoding.GetChars(bytes, 0, byteCount)[0];

        // Exit if it's a new line (/r/n or /n)
        if (thisChar == '\r') { continue; }
        if (thisChar == '\n') { break; }

        line.Append(thisChar);
    }
    return line.ToString();
}

【讨论】:

  • 另一种方法是使用BinaryReader 类,它允许读取编码字符,以及读取固定长度字节数组的能力。
【解决方案2】:

我假设您已经处理了所有的标头,可能包括 Content-Length 标头。我假设您现在想要阅读内容正文,可能是也可能不是文本内容。

我认为您能做的最好的事情是将整个内容正文作为流读取,然后在内容为文本的情况下将内容包装在 StreamReader 中:

List<string> lines = new List<string>();
byte[] responseBody = new byte[contentLength];
this.rtspStream.Read(responseBody, 0, contentLength);
if (contentIsText)
{
    using (var memoryStream = new MemoryStream(responseBody, 0, contentLength))
    {
        using (var reader = new StreamReader(memoryStream))
        {
            string line;
            while ((line = reader.ReadLine()) != string.Empty)
            {
                lines.Add(line);
            }
        }
    }
    // Do something with lines
}
else
{
    // Do something with responseBody
}

【讨论】:

  • 这不是问题,我看到的问题是当连接了阅读器时我根本无法调用stream.Read(),因为阅读器已经读取并缓冲了任何接收到的数据。要使用stream.Read(),我需要废弃StreamReader 来处理标头,这会使其余的读取变得复杂。
猜你喜欢
  • 2010-10-10
  • 2016-02-02
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
  • 2020-12-27
  • 2012-05-22
  • 2013-06-17
相关资源
最近更新 更多