片段长度前缀导致下一次从缓冲区读取的数据使用错误的消息长度
作者:互联网
我是来这里寻找其他人提出的问题的答案的人之一,我想我是自己较新的人,但是经过两天的搜索失败后,我决定该是时候问自己一个问题了.所以这是…
我有一个使用SocketAsyncEventArgs用C#、. NET 4,异步套接字编写的TCP服务器和客户端.我有一个长度为前缀的消息框架协议.总体而言,一切正常,但一个问题一直困扰着我.
情况是这样的(我将以小数为例):
可以说服务器的发送缓冲区长度为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个,因为这是我的长度前缀.因此,它是零长度的消息,被允许作为保持活动数据包.
剩余要读取的数据: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的注释(请参见上面的链接):
This class creates a single large buffer which can be divided up and
assigned to SocketAsyncEventArgs objects for use with each socket I/O
operation. This enables bufffers to be easily reused and gaurds
against fragmenting heap memory.
因此,问题很明显.欢迎提出任何有关如何解决此问题的建议:)他们所说的零散堆内存是否正确,可以“快速”创建数据缓冲区吗?如果是这样,当服务器扩展到几百甚至数千个客户端时,我会遇到内存问题吗?
解决方法:
这听起来像是您发送或接收代码中的错误.您应该只将BytesTransferred作为实际发送的数据,或者比分批到达时小的数字.我想知道的第一件事是:您是否正确设置了发送?即,如果您的缓冲区过大,则正确的实现可能如下所示:
args.SetBuffer(buffer, 0, actualBytesToSend);
if (!socket.SendAsync(args)) { /* whatever */ }
这里的realBytesToSend可以比buffer.Length小得多.我最初的怀疑是
您正在执行以下操作:
args.SetBuffer(buffer, 0, buffer.Length);
因此发送的数据量超出了您实际填充的数据量.
我应该强调:您的发送或接收中有问题;我不相信,至少在没有示例的情况下,这里的BCL中存在一些基本的潜在错误-我广泛使用了异步API,并且工作正常-但您确实需要准确跟踪在以下位置发送和接收的数据所有要点.
标签:asynchronous,tcp,protocol-buffers,c 来源: https://codeday.me/bug/20191127/2076092.html