我是那些来到这里寻找其他人提出的问题答案的人之一,而且我认为我自己更新问了什么,但是经过两天搜索失败后我决定是时候自己问一些问题了。所以这就是......
我有一个用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 反对分段堆内存。
所以这个问题非常清楚。关于如何解决这个问题的任何建议都是受欢迎的:)他们对碎片堆内存的看法是否正确,是否可以“动态”创建数据缓冲区?如果是这样,当服务器扩展到几百甚至几千个客户端时,我会遇到内存问题吗?
答案 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已经为您做到了。
您的服务器似乎存在根本性的错误。