TextReader.ReadLine返回不完整的行

时间:2009-03-01 16:09:06

标签: c# networking

我使用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>

我不希望“这是一个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);
}

感谢。

5 个答案:

答案 0 :(得分:3)

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

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

编辑:现在您已经发布了代码,原因更加清晰 - 您一次只能解码一个缓冲区。这真的不行,可能会以更严肃的方式打破。缓冲区甚至可能不会在字符边界处中断。

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

你肯定需要异步处理吗?

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

答案 1 :(得分:0)

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

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

请注意,这可以达到你想要的效果,但是,使用任何此类方案,您现在必须担心如何处理缓冲区太长的行。

- MarkusQ

答案 2 :(得分:0)

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

答案 3 :(得分:0)

这里可以看到几个问题:

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

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

答案 4 :(得分:0)

我决定编写自己的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,@ Novaorin和@Richard提出的非常有用的建议。你的共同努力使我得到了最终解决方案。 =]