Protobuf-net消息序列化大小属性

本文关键字:属性 序列化 消息 Protobuf-net | 更新日期: 2023-09-27 18:10:54

我们在一个公共协议基于Google protocol Buffers的应用程序中使用protobuf-net对消息进行序列化和反序列化。该库非常出色,它涵盖了我们的所有需求,除了这一点:我们需要在消息实际序列化之前找出序列化消息的字节长度。

这个问题在一年半前就已经问过了,根据Marc的说法,要做到这一点,唯一的方法是序列化到MemoryStream,然后读取.Length属性。这在我们的例子中是不可接受的,因为MemoryStream在后台分配一个字节缓冲区,我们必须避免这种情况。

来自同一响应的这一行给了我们希望,这终究是可能的:

如果你明确了用例是什么,我相信我们可以很容易地做到可用(如果还没有)。

这是我们的用例。我们的消息大小从几个字节到两个兆字节不等。应用程序预先分配用于套接字操作和序列化/反序列化的字节缓冲区,一旦预热阶段结束,就不能创建额外的缓冲区(提示:避免GC和堆碎片)。字节缓冲区基本上是池化的。我们还希望尽可能避免在缓冲区/流之间复制字节。

我们提出了两种可能的策略,它们都要求消息大小:

  1. 使用(大)固定大小的字节缓冲区,并序列化所有可以放入一个缓冲区的消息;使用Socket.Send发送缓冲区的内容。我们必须知道下一条消息何时无法放入缓冲区并停止序列化。如果没有消息大小,实现这一点的唯一方法是等待Serialize期间发生异常。
  2. 使用(小)可变大小字节缓冲区,并将每个消息序列化到一个缓冲区中;使用Socket.Send发送缓冲区的内容。为了从池中检查出大小合适的字节缓冲区,我们需要知道序列化消息有多少字节。

因为协议已经定义(我们不能改变这一点)并且要求消息长度前缀为Varint32,所以我们不能使用SerializeWithLengthPrefix方法。

那么是否有可能添加一个方法来估计消息大小而不序列化到流中?如果它不适合当前的功能集和库的路线图,但是可行的,我们有兴趣自己扩展库。我们也在寻找其他方法,如果有的话。

Protobuf-net消息序列化大小属性

如上所述,这不是立即可用的,因为代码有意尝试对数据进行单次传递(特别是IEnumerable<T>等)。但是,根据您的数据,它可能已经进行了适量的复制,以允许子消息也是长度前缀的事实,因此可能需要处理。通过在消息内部中使用"分组"子格式,可以大大减少这种混乱,因为分组允许只进行转发而不进行回溯。

那么是否有可能添加一个方法来估计消息大小而不序列化到流中?

估计几乎是无用的;因为没有终止符,所以它必须是精确的。最终,如果不实际操作,大小很难预测。在v1中有一些用于大小预测的代码,但目前似乎更倾向于单遍代码,并且在大多数情况下,缓冲区开销是名义上的(有适当的代码来重用内部缓冲区,以便它不会花费所有时间为小消息分配缓冲区)。

如果你的消息在内部是只转发的(分组的),那么一个欺骗可能是序列化到一个假流,测量,但放弃所有的数据;但是,您最终会序列化两次。

Re:

和要求消息长度前缀为Varint32,我们不能使用SerializeWithLengthPrefix方法

我不太确定我看到的关系-它允许一系列的格式等在这里使用;也许你能说得更具体些?

重新复制数据-我在这里玩的一个想法是使用亚标准形式的长度前缀。例如,可能在大多数情况下5个字节已经足够了,所以而不是处理,它可以留下5个字节,然后简单地覆盖而不压缩(因为八字节10000000仍然意味着"零并继续",即使它是冗余的)。这仍然需要缓冲(以允许回填),但不需要数据的移动。

最后一个简单的想法是:序列化到FileStream;然后写入文件长度,以及文件数据。