分段长度前缀导致从缓冲区读取的下一个数据使用不正确的消息长度

时间:2012-09-06 09:56:33

标签: c# asynchronous tcp protocol-buffers

我是那些来到这里寻找其他人提出的问题答案的人之一,而且我认为我自己更新问了什么,但是经过两天搜索失败后我决定是时候自己问一些问题了。所以这就是......

我有一个用C#,.NET 4编写的TCP服务器和客户端,使用 SocketAsyncEventArgs 的异步套接字。我有一个长度前缀的消息框架协议。总的来说一切都很好,但有一个问题一直困扰着我。

情况是这样的(我将使用小数字作为例子):

假设服务器的发送缓冲区长度为16个字节。 它发送一条 6字节长的消息,并在其前面加上 4字节长长度前缀。邮件总长度为6 + 4 = 10

客户端读取数据并接收长度为16字节的缓冲区(是10字节数据,6字节等于零)。

收到的缓冲区如下所示: 6 0 0 0 56 21 33 1 5 7 0 0 0 0 0 0

所以我读了前4个字节,这是我的长度前缀,我确定我的消息是6个字节长,我也读了它,到目前为止一切都很好。然后我有16-10 = 6个字节来阅读。所有这些都是零,我读了4个,因为它是我的长度前缀。所以这是一个零长度的消息,允许作为 keep-alive packet

要读取的剩余数据: 0 0

现在问题“开始”了。我只有 2个剩余字节要读,它们不足以完成 4字节长长度前缀缓冲区。所以我读了那两个字节,等待更多的传入数据。现在服务器不知道我还在读取长度前缀(我只是读取缓冲区中的所有零)并发送另一个正确前缀为4个字节的消息。客户端假设服务器发送丢失的2个字节。我在客户端接收数据,并读取前两个字节以形成一个完整的4字节长度缓冲区。结果是那样的

lengthBuffer = new byte [4] { 0,0,42,0 }

然后转换为 2752512 消息长度。因此,我的代码将继续读取下一个 2752512 字节以完成消息...

因此,在每个消息帧示例中,我都看到支持零长度消息作为keep-alive。我见过的每一个例子都没有比我做的更多。问题是,当我从服务器收到数据时,我不知道有多少数据需要读取。由于我用零填充部分填充缓冲区,所以我必须全部读取它,因为那些零可能是从连接的另一端发送的保持活动。

我可以删除零长度的消息并在第一个空消息后停止读取缓冲区,它应该解决这个问题,并使用自定义消息来保持活动机制。但我想知道我是否遗漏了某些东西,或者做错了什么,因为我见过的每个代码示例似乎都有同样的问题(?)

更新

马克·格拉维尔,你先生从我嘴里说出话来。即将更新问题是发送数据。问题是,最初在探索.NET Sockets和SocketAsyncEventArgs时,我遇到了这个示例:http://archive.msdn.microsoft.com/nclsamples/Wiki/View.aspx?title=socket%20performance 它使用可重用的缓冲池。简单地获取允许的预定义数量的最大客户端连接,例如10,获取最大单个缓冲区大小,例如512,并为所有这些创建一个大缓冲区。所以512 * 10 * 2(发送和接收)= 10240 所以我们有byte [] buff = new byte [10240]; 然后为每个连接它的客户端分配一个这个大缓冲区。第一个连接的客户端获得数据读取操作的前512个字节,并获得数据发送操作的下一个512字节(偏移512)。因此,代码最终已经分配了大小为512的发送缓冲区(正好是客户端稍后作为BytesTransferred接收的数字)。此缓冲区填充了数据,这512个字节中的所有剩余空间将作为零发送。

奇怪的是这个例子来自msdn。存在单个巨大缓冲区的原因是为了避免碎片堆内存,当缓冲区被固定并且GC无法收集它或类似的东西时。

在提供的示例中来自BufferManager.cs的注释(参见上面的链接):

  

该类创建一个可以分割的大缓冲区   分配给SocketAsyncEventArgs对象以用于每个套接字I / O.   操作。这使得缓冲器可以很容易地重复使用和gaurds   反对分段堆内存。

所以这个问题非常清楚。关于如何解决这个问题的任何建议都是受欢迎的:)他们对碎片堆内存的看法是否正确,是否可以“动态”创建数据缓冲区?如果是这样,当服务器扩展到几百甚至几千个客户端时,我会遇到内存问题吗?

3 个答案:

答案 0 :(得分:2)

我想问题是你正在处理你读作数据的缓冲区中的尾随零。这不是数据。这是垃圾。没有人把它发给你。

Stream.Read调用返回实际读取的字节数。你不应该以任何方式解释缓冲区的其余部分。

  

问题在于我不知道当我有多少数据需要阅读   从服务器接收。

是的,你这样做:使用Stream.Read的返回值。

答案 1 :(得分:1)

这听起来就像发送或接收代码中的错误。您应该获取BytesTransferred作为实际发送的数据,或者如果到达片段则小于该数字。我想知道的第一件事是:你是否正确设置了发送?即如果你有一个超大的缓冲区,正确的实现可能如下所示:

args.SetBuffer(buffer, 0, actualBytesToSend);
if (!socket.SendAsync(args)) { /* whatever */ }

其中actualBytesToSend可能远小于buffer.Length。我最初的怀疑是 你正在做类似的事情:

args.SetBuffer(buffer, 0, buffer.Length);

因此发送的数据比实际填充的数据多。

我应该强调:你的发送或接收都有问题;我不相信,至少没有一个例子,BCL中存在一些基本的底层错误 - 我广泛使用异步API,它工作正常 - 但你需要准确跟踪您在所有点发送和接收的数据。

答案 2 :(得分:0)

  

“现在服务器不知道我还在读取长度前缀(我只是读取缓冲区中的所有零)并发送另一条正确前缀为4个字节的消息。”。

为什么呢?服务器如何知道你是什么而不是在阅读?如果服务器重新发送消息的任何部分,则表示存在错误。 TCP已经为您做到了。

您的服务器似乎存在根本性的错误。