【问题标题】:Protocol buffer objects generated at runtime运行时生成的协议缓冲区对象
【发布时间】:2013-09-21 02:48:36
【问题描述】:

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

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

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

我想知道这是否是一个重要的想法,以及这种用例是否可能有一些实用程序。

谢谢

【问题讨论】:

  • 这不是DynamicMessage 的用途吗?虽然我不知道你说的是不是个好主意。
  • 需要看看。你能参考一些例子吗?不幸的是,javadoc 没有说太多。
  • 很遗憾,我没有例子。
  • 接受的答案详细解决了问题,除了我用另一个问题要求的扩展:stackoverflow.com/questions/19054580/…

标签: java protocol-buffers


【解决方案1】:

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

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

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

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

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

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

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

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

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

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

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

但这是个好主意吗?

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

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

【讨论】:

  • 这实际上是非常有用的答案。我需要一些时间才能完全尝试,但我正在慢慢前进:) 完成后会接受。
  • 如果您阅读了这篇文章,请您快速浏览一下stackoverflow.com/questions/19054580/…,看来您是经验丰富的 Protobuf 用户!谢谢
  • @kenton varda,你能提供我吗?如何从 .proto 文件创建 FileDescriptorProtos。我尝试过没有任何运气,如果您可以提供任何有效的链接,例如,将非常有帮助。
【解决方案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();

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

【讨论】:

  • 仅仅发布链接并不是回答问题的好方法,因为它可能会过期。
  • 添加了一些措辞和代码示例,希望对您有所帮助
  • 如何通过网络为特定的服务器或客户端发送消息?
猜你喜欢
  • 2021-01-04
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-24
  • 2021-02-06
  • 2022-08-19
相关资源
最近更新 更多