我是那些来这里寻找其他人提出的问题的答案的人之一,我想我自己也问过任何东西,但是在两天的搜索失败后,我决定是时候自己问一些问题了。所以这里...
我有一个使用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 操作使用。这使得缓冲区可以很容易地重用,并防止堆内存碎片化。
所以这个问题已经很清楚了。欢迎任何关于我应该如何解决这个问题的建议:) 他们所说的关于碎片堆内存的说法是否属实,“动态”创建数据缓冲区是否可以?如果是这样,当服务器扩展到数百甚至数千个客户端时,我是否会出现内存问题?