【问题标题】:TextReader.ReadLine returns incomplete linesTextReader.ReadLine 返回不完整的行
【发布时间】:2009-03-03 22:20:08
【问题描述】:

我使用Socket 通过 TCP 接收数据,并使用TextReader.ReadLine 从连接中读取行。存在未收到完整行的问题——TextReader.ReadLine 返回不完整的字符串。我希望它返回null,表示无法读取整行。我该怎么做?

基本上,我有这个数据传入:

"hello\nworld\nthis is a test\n"

当我运行ReadLine 时,我会得到这些回报:

"hello"
"world"
"this is a te"
<null>
<socket gets more data>
"st"
<null>

我不想返回“this is a te”。相反,我希望“这是一个测试”等到收到整行。

代码:

var endPoint = ...;
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
socket.Connect(endPoint);

var stream = new NetworkStream(socket, true);

var messageBuffer = new StringBuilder();

// Data received async callback (called several times).
int bytesRead = stream.EndRead(result);
string data = Encoding.UTF8.GetString(readBuffer.Take(bytesRead).ToArray());
messageBuffer.Append(data);

using(var reader = new StringReader(messageBuffer.ToString()))
{
    // This loop does not know that Message.Read reads lines.  For all it knows, it could read bytes or words or the whole stream.

    while((Message msg = Message.Read(reader)) != null)  // See below.
    {
        Console.WriteLine(msg.ToString());    // See example input/echo above.
    }

    messageBuffer = new StringBuilder(reader.ReadToEnd());
}

// Method of Message.
public static Message Read(TextReader reader)
{
    string line = reader.ReadLine();

    if(line == null)
        return null;

    return Message.FromRawString(line);
}

谢谢。

【问题讨论】:

  • 真的需要看你的代码,看看你是如何配置socket和TextReader的。
  • @Richard,它抽象了很多,但我会尝试提取必要的部分。

标签: c# networking


【解决方案1】:

听起来数据发送时带有一些额外的分隔符。假设您在网络流上使用 StreamReader,它的行为应该完全符合您的预期。我建议您使用 Wireshark 查看您的套接字正在接收的确切数据。

我也怀疑它返回 null 然后是另一行 - 你确定你不是说它返回一个空字符串然后是另一行吗?

编辑:现在您已经发布了代码,原因更清楚了 - 您一次只解码一个缓冲区。这真的行不通,并且可能以更严重的方式中断。缓冲区甚至可能不会在 character 边界处中断。

说实话,同步阅读和使用StreamReader 会容易得多。异步执行此操作,您应该使用System.Text.Decoder,如果需要,它可以存储任何先前的状态(从前一个缓冲区的末尾)。无论阅读了多少前一行,您还必须存储 - 我怀疑您根本无法使用 TextReader,或者至少您必须对 final 的情况进行特殊处理字符是 '\r' 或 '\n'。请记住,一个缓冲区可以以“\r”结尾,而下一个缓冲区可以以“\n”开头,表示它们之间有一个换行符。看看它有多难?

你确定,肯定需要异步处理这个吗?

编辑:听起来你可以做一些基本上可以将数据转储到的事情,并附加一个“LineCompleted”事件处理程序。您可以先附加事件处理程序,然后继续将数据转储到其中,直到没有更多数据为止(此时您需要告诉它数据已完成)。如果这听起来合适,我可能会尝试为 MiscUtil 编写这样一门课程 - 但我不太可能在下周内完成它(我现在真的很忙)。

【讨论】:

  • 我不肯定,肯定需要异步访问,不。但它绝对简化了我的代码(至少对于我目前的结构)。我对 \r 和 \n 以及 \r\n 和 \n\r 都被视为换行符感到满意(事实上我希望如此)。
  • @strager:将 "\r\n" 视为单个换行符的关键在于,如果 \r 位于前一个缓冲区的末尾并且 \n 位于下一个的开始。有很多状态需要担心。您可能想看看我的 ReverseLineReader 必须处理(续)
  • 有类似的问题——事实上,很多代码翻译起来相对容易。与仅同步使用 StreamReader 相比,它仍然会很痛苦。您能否异步阅读全部内容并然后对其进行处理?这可能适用于短连接。
  • 顺便说一下,R​​everseLineReader 的代码在stackoverflow.com/questions/452902/…
  • @Jon Skeet,空消息被忽略,所以这不是什么大问题。我不明白您所说的评论“您能异步阅读全部内容并然后处理它吗?”是什么意思? ——你能详细说明一下吗?无论如何,我都会调查您的 ReverseLineReader 课程。
【解决方案2】:

有一个缓冲区(开始为空),并且每次阅读时

  • 如果缓冲区中有\n,则删除所有内容 一切都包括它并返回它
  • 读取您可以读取的内容,并将您读取的内容附加到缓冲区
  • 如果由于 eof 导致读取失败,则返回并清除内容,除非缓冲区为空,在这种情况下传播 eof。
  • 如果您阅读的内容中有\n,请从顶部重试, 否则返回空

请注意,这将满足您的要求,但是,对于任何此类方案,您现在必须担心如何处理对于您的缓冲区来说太长的行。

-- MarkusQ

【讨论】:

  • 我已经使用了缓冲方法(参见示例代码)。你是说我应该在调用 ReadLine 之前手动检查我的行尾标记?
【解决方案3】:

查看我对previous very similar question 的回复。它以类似流的方式与异步套接字 I/O 和读取行相关。希望对您有所帮助。

【讨论】:

  • 啊哈。我以前看过你的代码,并窃取了大部分想法。但是,我没有偷走重要的部分,这就是我遇到问题的原因。将实施并报告。 =]
  • 好的,有问题。实际的 ReadLine 是在其他一些函数中完成的。我的读取循环不知道 ReadLine。我会更新我的帖子以反映这个问题。
  • 该答案使用 ASCII 编码,这使生活变得更轻松,但不会处理任何非 ASCII 文本(当然)。使用 UTF-8 有点困难,其中“读取”可能会在字符中途完成。
  • @Jon Skeet,我需要 UTF-8 支持,所以这是这个答案的另一个问题。
  • 是的,Jon Skeet 的观点也很好。正如他所指出的那样,您可以通过使用 Decoder 类来解决这个问题(或者通过反映 StreamReader 来做一些丑陋的黑客攻击)。无论哪种方式,恐怕都不会简单......
【解决方案4】:

这里有几个问题:

  1. 单个 Unicode 代码点可以跨数据包拆分,因此您需要保留自己的 Utf8Encoding 实例。或者将完整的消息缓冲为 byte[] 并在您知道它完成时一次性转换。

  2. 您需要一种确定何时收到完整消息的方法。您需要继续阅读直到完成(并处理在同一个 Read 调用中开始接收下一个数据包的情况。

【讨论】:

  • 第二个问题是因为只有 Message.Read 应该知道消息何时完成(如果未完成则返回 null)。我该怎么做?顺便说一句,我已经缓冲了我的数据。
  • 消息的划分必须是缓冲的一部分。通过网络,网络的物理现实无法完全抽象出来。
【解决方案5】:

我决定编写自己的 ReadLine 解析器。代码如下:

// Async callback.
Message message;

while((message = Message.ReadBytes(messageBuffer)) != null)
{
    OnMessageReceived(new MessageEventArgs(message));
}

// Message class.
public static Message ReadBytes(List<byte> data)
{
    int end = data.FindIndex(b => b == '\n' || b == '\r');

    if(end == -1)
        return null;

    string line = Encoding.UTF8.GetString(data.Take(end).ToArray());

    data.RemoveRange(0, end + 1);

    if(line == "")
        return ReadBytes(data);

    if(line == null)
        return null;

    return Message.FromRawString(line);
}

非常感谢@Jon Skeet、@Noldorin 和@Richard 提供的非常有用的建议。您的共同努力使我找到了最终解决方案。 =]

【讨论】:

  • 为什么要通过引用传递“数据”?您永远不会在 ReadBytes 中分配给它。如果您打算使用这种方法,我相信还有更有效的方法。
  • @Jon Skeet,因为我正在修改它的数据。也许我不需要这个?我来自 C++ 背景,将使用引用。 (我正在使用 data.RemoveRange 来修改数据。)
  • @Jon Skeet,啊,显然我不需要这个。 (现在我觉得不安全,因为随机方法可以修改我的引用类型!)感谢您提及。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-02
  • 2021-12-15
  • 1970-01-01
  • 2014-12-12
  • 2014-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多