运行时生成的协议缓冲区对象

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

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

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

我想知道这是否是至关重要的想法,如果这种用例可能有一些实用性。

谢谢

您所描述的实际上已经被C ++和Java中的Protocol Buffers实现所支持。 您所要做的就是传输一个FileDescriptorSet (在google/protobuf/descriptor.proto定义),其中包含代表每个相关.proto文件的FileDescriptorProto ,然后使用DynamicMessage来解释接收端的消息。

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

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

将所有需要的FileDescriptorProto以及它们导入的所有文件放入FileDescriptorSet原型中。 请注意,您可以使用google::protobuf::FileDescriptorFoo::descriptor().file() )迭代依赖项,而不是显式地命名每个依赖项。

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

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

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

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

同样,您可以调用DynamicMessage.parseFrom(descriptor,input)来解析从服务器接收的消息。

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

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

但这是个好主意吗?

嗯,这取决于。 客户端实际上要从服务器接收的这个模式什么? 通过线路传输模式并不能使客户端与该协议版本兼容 – 客户端仍然必须了解协议的含义。 如果协议已经以向后不兼容的方式进行了更改,这几乎肯定意味着协议的含义已经改变,客户端代码必须更新,模式传输与否。 客户端只进行仅依赖于消息内容但不依赖于消息含义的通用操不必知道它意味着什么。 但这是相对不寻常的,特别是在应用程序的客户端。 这正是Protobufs默认不发送任何类型信息的原因 – 因为它通常是无用的,因为如果接收者不知道其含义 ,那么模式就无关紧要了。

如果问题是服务器正在向客户端发送根本不打算解释的消息,而只是稍后发送回服务器,则客户端根本不需要该模式。 只需将消息作为bytes传输,不要费心解析它。 请注意,包含Foo类型的编码消息的bytes字段在线Foo看起来与其类型实际声明为Foo的字段完全相同。 您实际上可以针对.proto文件的略微不同版本编译客户端和服务器,其中客户端将特定字段视为bytes而服务器将其视为子消息,以避免客户端需要注意该子消息的定义。 “

对于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(); 

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