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

时间:2013-09-16 20:29:37

标签: java protocol-buffers

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

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

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

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

由于

2 个答案:

答案 0 :(得分:23)

您所描述的内容实际上已经被C ++和Java中的Protocol Buffers实现所支持。您所要做的就是发送一个FileDescriptorSet(在google/protobuf/descriptor.proto中定义),其中包含代表每个相关FileDescriptorProto文件的.proto,然后使用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必须能够处理任何类型。

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

但这是个好主意吗?

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

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

答案 1 :(得分:6)

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

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