13

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

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

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

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

谢谢

4

2 回答 2

32

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

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

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

FileDescriptorProto定义所需类型的所有 s 以及它们导入的所有文件放入FileDescriptorSetproto 中。请注意,您可以使用google::protobuf::FileDescriptor(返回的东西Foo::descriptor().file())来迭代依赖项,而不是显式地命名每个依赖项。

现在,发送FileDescriptorSet给客户端。

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

从那里,您可以使用FileDescriptor'sfindMessageTypeByName()来查找Descriptor您关心的特定消息类型。

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

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

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

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

但这是个好主意吗?

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

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

于 2013-09-17T21:01:37.683 回答
12

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

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

于 2016-02-02T06:39:38.013 回答