3

我是那些来这里寻找其他人提出的问题的答案的人之一,我想我自己也问过任何东西,但是在两天的搜索失败后,我决定是时候自己问一些问题了。所以这里...

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

情况是这样的(我以小数为例):

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

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

接收到的缓冲区如下所示: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 字节长的前缀缓冲区。所以我读取了这 2 个字节,并等待更多的传入数据。现在服务器不知道我仍在读取长度前缀(我只是读取缓冲区中的所有零)并发送另一条正确前缀为 4 个字节的消息。并且客户端假设服务器发送那些丢失的 2 个字节。我在客户端接收数据,并读取前两个字节以形成一个完整的 4 字节长度缓冲区。结果是这样的

lengthBuffer = 新字节[4]{ 0, 0, 42, 0 }

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

因此,在每个消息框架示例中,我都看到零长度消息被支持为保持活动状态。我看到的每个例子都没有比我做的更多。问题是当我从服务器接收数据时,我不知道我必须读取多少数据。由于我有部分填充为零的缓冲区,因此我必须将其全部读取,因为这些零可能是我从连接的另一端发送的保持活动状态。

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

更新

Marc Gravell,你先生从我嘴里说出来的话。即将更新问题在于发送数据。问题是,最初在探索 .NET 套接字和 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 操作使用。这使得缓冲区可以很容易地重用,并防止堆内存碎片化。

所以这个问题已经很清楚了。欢迎任何关于我应该如何解决这个问题的建议:) 他们所说的关于碎片堆内存的说法是否属实,“动态”创建数据缓冲区是否可以?如果是这样,当服务器扩展到数百甚至数千个客户端时,我是否会出现内存问题?

4

3 回答 3

2

我想问题是您将读取的缓冲区中的尾随零视为数据。这不是数据。这是垃圾。从来没有人寄给你。

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

问题是当我从服务器接收数据时,我不知道我必须读取多少数据。

是的,你这样做:使用来自Stream.Read.

于 2012-09-06T10:04:51.457 回答
1

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

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

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

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

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

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

于 2012-09-06T10:49:56.207 回答
0

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

为什么?服务器如何知道你在读什么,没有读到什么?如果服务器重新传输消息的任何部分,则它是错误的。TCP 已经为您做到了。

您的服务器似乎有一些根本性的问题。

于 2012-09-06T10:05:20.833 回答