protocol buffers(三)

这篇文章翻译自https://developers.google.com/protocol-buffers/

Oneof

如果一个Message拥有一堆字段,但同时只有一个字段会被赋值,可以通过Oneof功能来节省内存。
Oneof字段会将多个常规字段共享一块内存,同时只有一个字段会被赋值。赋值任意字段会清除其他已经赋值的字段。你可以通过case()或者WhichOneof()方法检查哪个字段被赋值,根据语言不通检查方法也不同。

使用 Oneof

在proto文件中通过Oneof关键字定义一个oneof类型,下面是test_oneof例子:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

可以添加oneof字段,可以添加任意类型字段,但是不能使用repeated 。

Oneof 特征

  • 设置任意值会清除其他字段赋值的值。所以如果你赋值了多个字段,只有最后一个赋值的字段会有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
  • oneof 不能使用repeated
  • 反射api对oneof类型好用。
  • 如果使用c++,请确保不要导致内存崩溃。下面的例子演示了内存崩溃,因为sub_message 已经被set_name()清除掉了。
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here
  • 在C++中,swap()方法会交换内存。下面的例子中msg2将拥有name而msg1将拥有sub_message
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());

向后兼容性问题

添加或者删除oneof中的字段要小心。如果检查oneof返回None/NOT_SET,它可能表示oneof没有被赋值,也可能表示赋值给了新增的字段。这里无法区分是哪种情况发生。

标签值重用问题

  • 移出或者移入一个字段到oneof,Message在序列化和反序列化之后可能会丢失信息。
  • 删除并重新添加字段到oneof,Message在序列化和反序列化之后可能会清除字段信息。
  • 拆分或者合并oneof,与上面两个问题类似。

Maps

如果你想创建一个Map在你的数据格式定义中,protocol提供一个简单的语法:

map map_field = N;

key_type可以是数值类型或者字符串类型。enum不能作为key_type类型。value_type可以是除了map类型其他任意类型。
例如,定义一个Projects的Map,每个Project 对应一个string类型的键。你可以想下面那样定义:

map projects = 3;
  • Map字段不能是 repeated.
  • Map中值的排序是不一定的。

向后兼容性

map格式化后与下面是一致的,所以即使protocol buffer实现不支持map,一样可以获取数据。

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

Packages

你可以添加一个可选的package,预防不同的proto文件中Message名称冲突。

package foo.bar;
message Open { ... }

你可以使用package指定Message字段类型

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

不同语言对于package的不同处理:

  • 在c++中,生成的class会被放到namespace中,上面的例子,Open会在namespace foo::bar中。
  • 在java中,会按照java的package生成,除非你指定java_package在proto文件开头。
  • 在Python中,package会被忽略,因为python加载是根据系统文件目录来的。
  • 在go语言中,会按照go的package生成,除非你指定go_package在proto文件开头。

定义 Services

如果你想用你定义的Message完成一个RPC (Remote Procedure Call)访问,你可以定义一个RPC服务在proto文件中,protocol buffer编译器会根据你选择的语言生成对应的接口。例如,你想定义一个RPC方法,参数是SearchRequest,返回值是SearchResponse,proto文件中应如下定义:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

使用protocol buffer的RPC系统,最直接的就是gRPC:Google开发的一个多语言,跨平台,开源的RPC系统。gRPC可以和protocol buffer很好的工作,你可以通过proto文件直接生成对应的RPC代码。
这里也有一些第三方实现的RPC使用Protocol Buffer,查看 third-party add-ons wiki page。

你可能感兴趣的:(protocol buffers(三))