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

Jan*_*yka 12 java protocol-buffers

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

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

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

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

谢谢

Ken*_*rda 24

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

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

google::protobuf::FileDescriptorProto file;
Foo::descriptor().file()->CopyTo(&file);
Run Code Online (Sandbox Code Playgroud)

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

现在,发送FileDescriptorSet给客户端.

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

从那里,您可以使用FileDescriptor's findMessageTypeByName()来查找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而服务器将其视为子消息,以避免客户端需要知道定义那个子消息.``


osu*_*ciu 7

对于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();
Run Code Online (Sandbox Code Playgroud)

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