运行时生成的协议缓冲区对象但这是一个好主意吗?

2022-09-02 23:12:10

我的一位同事提出了在运行时生成协议缓冲区类的想法。意义:

  • 有C++服务器应用程序和 Java 客户机应用程序通过协议缓冲区消息通过 TCP/IP 进行通信。
  • C++应用程序可能在不同版本中具有不同的架构,这不一定是向后兼容的
  • 有 Java 应用程序与此服务器通信,它应该支持所有可能的服务器版本。

这个想法是,服务器将协议缓冲区的定义作为初始握手的一部分发送,Java应用程序在运行时生成类并使用它来与服务器进行通信。

我想知道这是否是至关重要的想法,以及这种用例是否有一些实用性。

谢谢


答案 1

您描述的内容实际上已经得到了C++和Java中的协议缓冲区实现的支持。您所要做的就是传输一个(如google/protobuf/descriptor.proto中所定义)包含表示每个相关文件的s,然后用于解释接收端的消息。FileDescriptorSetFileDescriptorProto.protoDynamicMessage

要获取 in C++,给定该文件中定义的消息类型,请执行以下操作:FileDescriptorProtoFoo

google::protobuf::FileDescriptorProto file;
Foo::descriptor().file()->CopyTo(&file);

将定义所需类型的所有 s 以及它们导入的所有文件放入原型中。请注意,您可以使用 google::p rotobuf::FileDescriptor(返回的内容)来迭代依赖项,而不是显式命名每个依赖项。FileDescriptorProtoFileDescriptorSetFoo::descriptor().file()

现在,将 发送到客户端。FileDescriptorSet

在客户端上,使用 FileDescriptor.buildFrom() 将每个都转换为实时描述符.FileDescriptor。您必须确保在依赖项之前构建依赖项,因为在构建依赖项时,您必须提供已构建的依赖项来构建From()。FileDescriptorProto

从那里,您可以使用 FileDescriptorfindMessageTypeByName() 来查找您关注的特定消息类型的描述符

最后,您可以调用 DynamicMessage.newBuilder(描述符)来为相关类型构造一个新的生成器实例。DynamicMessage.Builder 实现了 Message.Builder 接口,该接口具有 getField()setField() 等字段,用于动态操作消息的字段(通过指定相应的 FieldDescriptors)。

同样,您可以调用 DynamicMessage.parseFrom(描述符,输入)来解析从服务器接收的消息。

请注意,动态消息的一个缺点是它相对较慢。从本质上讲,它就像一种解释型语言。生成的代码速度更快,因为编译器可以针对特定类型进行优化,而 DynamicMessage 必须能够处理任何类型。

但是,这真的没有办法解决这个问题。即使您在运行时运行代码生成器并编译了该类,在您知道要使用的类型之前,实际使用新类的代码仍然是您之前编写的代码。因此,它仍然必须使用反射或类似反射的接口来访问消息,这将比为特定类型手写代码要慢。

但这是一个好主意吗?

好吧,这取决于。客户端实际上将如何处理从服务器接收的此架构?通过网络传输模式并不能神奇地使客户端与该协议的版本兼容 - 客户端仍然必须了解协议的含义。如果协议以向后不兼容的方式进行了更改,这几乎肯定意味着协议的含义已更改,并且必须更新客户端代码,无论是否进行架构传输。唯一可以期望客户端在没有更新的情况下继续工作的情况是客户端仅执行仅依赖于消息内容但不依赖于消息含义的通用操作 - 例如,客户端可以将消息转换为 JSON,而不必知道它的含义。但这是相对不寻常的,特别是在应用程序的客户端。这正是Protobufs默认情况下不发送任何类型信息的原因 - 因为它通常是无用的,因为如果接收者不知道含义,架构就无关紧要。

如果问题是服务器向客户端发送的消息根本不打算解释,而只是在以后发送回服务器,那么客户端根本不需要架构。只需将消息传递为,不要费心解析它。请注意,包含类型编码消息的字段在网络上看起来与类型实际声明为 的字段完全相同。您实际上可以针对略有不同的文件版本编译客户端和服务器,其中客户端看到的是特定字段,而服务器将其视为子消息,以避免客户端需要了解该子消息的定义。``bytesbytesFooFoo.protobytes


答案 2

对于Java,您可能会发现以下包装器API(“protobuf-dynamic”)比原始的protobuf API更易于使用:

https://github.com/os72/protobuf-dynamic

例如:

// Create dynamic schema
DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder();
schemaBuilder.setName("PersonSchemaDynamic.proto");

MessageDefinition msgDef = MessageDefinition.newBuilder("Person") // message Person
    .addField("required", "int32", "id", 1)     // required int32 id = 1
    .addField("required", "string", "name", 2)  // required string name = 2
    .addField("optional", "string", "email", 3) // optional string email = 3
    .build();

schemaBuilder.addMessageDefinition(msgDef);
DynamicSchema schema = schemaBuilder.build();

// Create dynamic message from schema
DynamicMessage.Builder msgBuilder = schema.newMessageBuilder("Person");
Descriptor msgDesc = msgBuilder.getDescriptorForType();
DynamicMessage msg = msgBuilder
    .setField(msgDesc.findFieldByName("id"), 1)
    .setField(msgDesc.findFieldByName("name"), "Alan Turing")
    .setField(msgDesc.findFieldByName("email"), "at@sis.gov.uk")
    .build();

动态架构在某些应用程序中非常有用,可以在不重新编译代码的情况下分发更改(例如在更动态的类型系统中)。它们对于不需要语义理解的“哑”应用程序(例如数据浏览器工具)也非常有用。


推荐