C# 处理基于线路的网络I/O流的好方法是什么?

C# 处理基于线路的网络I/O流的好方法是什么?,c#,.net,asynchronous,network-programming,C#,.net,Asynchronous,Network Programming,注:让我对这个问题的长度表示歉意,我必须在其中输入大量信息。我希望这不会导致太多人简单地浏览它并做出假设。请完整阅读。谢谢 我有一个数据流通过套接字传入。此数据是面向行的 我正在使用.NET(BeginRead等)的APM(异步编程方法)。这就排除了使用基于流的I/O,因为异步I/O是基于缓冲区的。可以重新打包数据并将其发送到流(如内存流),但也存在一些问题 问题是我的输入流(我无法控制)没有给我任何关于流的长度的信息。它只是一个新行流,看起来像这样: COMMAND\n ...Unpredic

注:让我对这个问题的长度表示歉意,我必须在其中输入大量信息。我希望这不会导致太多人简单地浏览它并做出假设。请完整阅读。谢谢

我有一个数据流通过套接字传入。此数据是面向行的

我正在使用.NET(BeginRead等)的APM(异步编程方法)。这就排除了使用基于流的I/O,因为异步I/O是基于缓冲区的。可以重新打包数据并将其发送到流(如内存流),但也存在一些问题

问题是我的输入流(我无法控制)没有给我任何关于流的长度的信息。它只是一个新行流,看起来像这样:

COMMAND\n
...Unpredictable number of lines of data...\n
END COMMAND\n
....repeat....
因此,使用APM,由于我不知道任何给定数据集的长度,数据块很可能会跨越缓冲区边界,需要多次读取,但这些多次读取也会跨越多个数据块

例如:

Byte buffer[1024] = ".................blah\nThis is another l"
[another read]
                    "ine\n.............................More Lines..."
// Note: no newline at the end
StringBuilder sb = new StringBuilder("This is a line\nThis is incomp..");
StringReader sr = new StringReader(sb);
string s = sr.ReadLine(); // returns "This is a line"
s = sr.ReadLine();        // returns "This is incomp.."
我的第一个想法是使用StringBuilder并简单地将缓冲行附加到SB。这在某种程度上是可行的,但我发现很难提取数据块。我尝试使用StringReader读取换行的数据,但无法知道是否得到完整的行,因为StringReader在添加的最后一个块的末尾返回部分行,随后返回null aftewards。没有办法知道返回的是否是完整的换行数据

例如:

Byte buffer[1024] = ".................blah\nThis is another l"
[another read]
                    "ine\n.............................More Lines..."
// Note: no newline at the end
StringBuilder sb = new StringBuilder("This is a line\nThis is incomp..");
StringReader sr = new StringReader(sb);
string s = sr.ReadLine(); // returns "This is a line"
s = sr.ReadLine();        // returns "This is incomp.."
更糟糕的是,如果我只是继续添加数据,缓冲区会越来越大,而且由于每次可能运行数周或数月,这不是一个好的解决方案

我的下一个想法是在我读取数据块时从SB中删除数据块。这需要编写自己的ReadLine函数,但在读写过程中,我一直无法锁定数据。此外,较大的数据块(可能包含数百次读取和兆字节的数据)需要扫描整个缓冲区以查找换行符。它效率不高,而且很难看

我正在寻找一种既有StreamReader/Writer的简单性,又有异步I/O的方便性的东西

我的下一个想法是使用MemoryStream,将数据块写入内存流,然后将StreamReader附加到该流并使用ReadLine,但我同样不知道缓冲区中最后一次读取的数据是否是完整的行,而且从流中删除“过时”数据更难

我还考虑过使用同步读取的线程。这样做的好处是,使用StreamReader时,它将始终从ReadLine()返回完整的一行,断开连接的情况除外。但是,这在取消连接方面存在问题,某些类型的网络问题可能会导致长时间挂起阻塞套接字。我使用异步IO是因为我不想在程序在数据接收时阻塞线程

这种联系是持久的。随着时间的推移,数据将继续流动。在初始连接期间,存在大量数据流,一旦数据流完成,套接字将保持打开状态,等待实时更新。我不知道初始流何时“完成”,因为唯一知道的方法是不再立即发送数据。这意味着我不能等待初始数据加载完成后再进行处理,我几乎无法“实时”处理它


那么,有谁能提出一个好的方法来处理这种情况,而不是过于复杂?我真的希望这是尽可能简单和优雅,但我不断提出越来越复杂的解决方案,由于所有的边缘情况。我想我想要的是某种FIFO,在这种FIFO中,我可以轻松地继续添加更多数据,同时从中弹出符合特定条件的数据(即,换行符终止的字符串)。

您在“您的问题”中解释的内容,让我想起了ASCIZ字符串。(). 这可能是一个有益的开始

在大学里,我不得不为我正在做的一个项目写一些类似的东西。不幸的是,我控制了发送套接字,所以我插入了一个消息字段长度作为协议的一部分。然而,我认为类似的方法可能会让你受益

我是如何处理我的解决方案的,我会发送类似于5HELLO的东西,所以首先我会看到5,并且知道我的消息长度是5,因此我需要的消息长度是5个字符。然而,如果在我的异步读取中,我只得到5HE,我会看到我的消息长度为5,但我只能从线路上读取3个字节(假设是ASCII字符)。正因为如此,我知道我丢失了一些字节,并将我所拥有的存储在片段缓冲区中。我每个套接字有一个片段缓冲区,因此避免了任何同步问题。这个粗略的过程很简单

  • 从套接字读取到字节数组中,记录读取的字节数
  • 逐字节扫描,直到找到换行符(如果您没有接收ascii字符,这会变得非常复杂,但可能是多个字节的字符,您可以自己处理)
  • 将您的frag buffer转换为字符串,并将您的read buffer追加到新的一行。将此字符串作为已完成的消息放到队列或要处理的它自己的委托上。(您可以通过将读套接字写入与片段相同的字节数组来优化这些缓冲区,但这很难解释)
  • 继续循环,每次我们找到新行时,从记录的开始/结束位置的字节排列创建一个字符串,并放到队列/委托上进行处理
  • 一旦我们到达读取缓冲区的末尾,将剩余的任何内容复制到frag缓冲区中
  • 调用套接字上的beginhead,它将跳转到步骤1