我们正在使用 protobuf 来使多个系统相互通信。其中一些使用 protobuf.net,一些使用其他语言和库。 我们的应用程序级消息由标头和正文组成。
在 .net 世界中,我们有:
public class ProtobufMessage
{
public IList<byte[]> Frames { get; }
}
RuntimeModel = RuntimeTypeModel.Create();
RuntimeModel.Add(typeof(ProtobufMessage), false).Add(1, nameof(ProtobufMessage.Frames));
(由于消息为 false:类似元组的类型不支持此操作。要禁用类似元组的类型发现,请在首次将类型添加到模型时使用 applyDefaultBehaviour: false。)
当消息到达时,我们获取 header Frame,将其反序列化,现在我们可以获取正文的类型来反序列化下一个 Frame (byte[]):
byte[] bytes; // Body Frame
using var memory = new MemoryStream(bytes);
return typeModel.Deserialize<T>(memory); // T: type from the Header frame
当数据类型是类时,一切正常。
但有时,主体数据只是
repeated int32
,因此,没有将原型合约添加到 RuntimeModel。
假设我们发送 [1,2,3] 作为正文(标签 1)
当数据到达时:080108020803(解压)主体帧解码正常
在 proto3 中,标量数字类型的重复字段默认使用压缩编码。
但是当 protobuf-net 尝试反序列化时:0a03010203 抛出异常:无效的线路数据(字符串)
我们做错了什么吗? 当没有类来装饰时,如何告诉 protobuf 传入的数据已打包?
感觉有点像GPB没有被正确使用。在 GPB 中,您不需要先发送一条“标头”消息来告诉您下一条消息是什么; GPB 提供了这种“下一步是什么?”的信息。情况;这就是
oneof
的用途。关键字 oneof
是一种自我描述;它包含“以下之一”。
如果有一系列消息类型可以从一个地方发送到另一个地方,最好的办法是将所有这些类型合并到带有
oneof
字段的单个消息中。
message A
{
..
}
message B
{
..
}
message MessageWrapper
{
oneof msg
{
A a = 1;
B b = 2;
}
}
这个想法是,所有您序列化/发送/接收/反序列化的都是 MessageWrappers,反序列化后,您询问 MessageWrapper 对象它包含的一个的变体。
副作用是您有一个针对任何消息类型的类。
关于如何处理
repeated int32
,我认为通过检查使用 .proto
编译描述消息的 protoc
文件会发生什么,可以学到很多东西。您最终不会得到独立的标量类型,您最终会得到一个以标量类型作为成员的 C# 类。我认为这是一个很大的暗示。
此外,是否有任何特殊原因要先编写项目代码,然后在运行时构建?您说您有多种语言的多个系统。最简单的事情,实际上是 Google 创建 GPB 的具体原因之一,就是拥有一个 .proto 文件,您可以将其编译为 c#、java、c++,无论您的项目需要什么。 .proto 文件是整个项目的“单一事实点”,项目的所有成员都接受该文件,对其进行编译,保证成功的互操作性。您似乎正在采取的方法 - 用 C# 编写类并装饰它们,以便它们可以序列化为 GPB 有线格式 - 意味着
最好有一个 .proto 文件并编译它。