ProtoBuf 总结(一)语法指引

本文目录


protocol buffers 是什么?

Protocol buffers 最初是在 Google 开发的,用于处理索引服务器请求/响应协议。

protocol buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小、更快、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏根据旧数据结构编译而成并且已部署的程序。


为什么不使用 XML?

对于序列化结构数据,protocol buffers 比 XML 更具优势。Protocol buffers:

  • 更简单
  • 小 3 ~ 10 倍
  • 快 20 ~ 100 倍
  • 更加清晰明确
  • 自动生成更易于以编程方式使用的数据访问类

protocol buffers是如何工作的?

你可以通过在 .proto 文件中定义 protocol buffer message 类型,来指定你想如何对序列化信息进行结构化。每一个 protocol buffer message 是一个信息的小逻辑记录,包含了一系列的 name-value 对。这里有一个非常基础的 .proto 文件样例,它定义了一个包含 "person" 相关信息的 message:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

正如你所见,message 格式很简单 - 每种 message 类型都有一个或多个具有唯一编号的字段,每个字段都有一个名称和一个值类型,其中值类型可以是数字(整数或浮点数),布尔值,字符串,原始字节,甚至(如上例所示)其它 protocol buffer message 类型,这意味着允许你分层次地构建数据。你可以指定 optional 字段,required 字段和 repeated 字段。

PS: proto3 引入了新的语言版本,并舍弃 required 字段,因为 required 字段通常被认为是有害的并且违反了 protobuf 的兼容性语义。

  • required关键字

该关键字修饰的变量必须存在,数据发送方和接收方都必须处理这个字;

  • optional关键字

该关键字为可选,例:protobuf处理的一个optional关键字修饰的bool的变量,发送方在发送的时候,如果这个字段有值,那么就给bool变量标记为true,否则就标记为false,接收方在收到这个字段的同时,也会收到发送方同时发送的bool变量,拿着bool变量就知道这个字段是否有值了,这就是option的意思。

  • repeated关键字

重复,对应变量为数组。

一旦定义了 messages,就可以在 .proto 文件上运行 protocol buffer 编译器来生成指定语言的数据访问类。这些类为每个字段提供了简单的访问器(如 name()和 set_name()),以及将整个结构序列化为原始字节和解析原始字节的方法 - 例如,如果你选择的语言是 C++,则运行编译器上面的例子将生成一个名为 Person 的类。然后,你可以在应用程序中使用此类来填充,序列化和检索 Person 的 messages。于是你可以写一些这样的代码:

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

 之后,你可以重新读取解析你的 message

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

proto2-语法指引-1

首先让我们看一个非常简单的例子。假设你要定义一个搜索请求的 message 格式,其中每个搜索请求都有一个查询字符串,你感兴趣的特定结果页数(第几页)以及每页的结果数。下面就是定义这个请求的 .proto 文件:

message SearchRequest {
  required string query = 1;  // 查询字符串
  optional int32 page_number = 2;  // 第几页
  optional int32 result_per_page = 3;  // 每页的结果数
}

SearchRequest message 定义指定了三个字段(名称/值对),每个字段对应着要包含在 message 中的数据,每个字段都有一个名称和类型。

在上面的示例中,所有字段都是 标量类型:两个整数(page_number 和 result_per_page)和一个字符串(query)。但是,你还可以为字段指定复合类型,包括 枚举 和其它的 message 类型。

如你所见,message 定义中的每个字段都有唯一编号。这些数字以 message 二进制格式 标识你的字段,并且一旦你的 message 被使用,这些编号就无法再更改。请注意,1 到 15 范围内的字段编号需要一个字节进行编码,16 到 2047 范围内的字段编号占用两个字节。因此,你应该为非常频繁出现的 message 元素保留字段编号 1 到 15。请记住为将来可能添加的常用元素预留出一些空间。

此外,你也不能使用 19000 到 19999 范围内的数字,因为它们是为 Protocol Buffers 的实现保留的。


proto2-语法指引-2

你指定的 message 字段可以是下面几种情况之一:

  • required: 格式良好的 message 必须包含该字段一次。
  • optional: 格式良好的 message 可以包含该字段零次或一次(不超过一次)。
  • repeated: 该字段可以在格式良好的消息中重复任意多次(包括零)。其中重复值的顺序会被保留。

为你的 .proto 文件添加注释,可以使用 C/C++ 语法风格的注释 // 和 /* ... */ 。

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  required string query = 1;
  optional int32 page_number = 2;  // Which page number do we want?
  optional int32 result_per_page = 3;  // Number of results to return per page.
}

你的 .proto 文件将生成什么?

当你在 .proto 上运行 protocol buffer 编译器时,编译器将会生成所需语言的代码,这些代码可以操作文件中描述的 message 类型,包括获取和设置字段值、将 message 序列化为输出流、以及从输入流中解析出 message。

  • 对于 C++,编译器从每个 .proto 生成一个 .h 和 .cc 文件,其中包含文件中描述的每种 message 类型对应的类。
  • 对于 Java,编译器为每个 message 类型生成一个 .java 文件(类),以及用于创建 message 类实例的特殊 Builder 类。
  • 对于Python 有点不同 - Python 编译器生成一个模块,其中包含 .proto 中每种 message 类型的静态描述符,然后与元类一起使用以创建必要的 Python 数据访问类。
  • 对于 Go,编译器会生成一个 .pb.go 文件,其中包含对应每种 message 类型的类型。
    你可以按照所选语言的教程了解更多有关各种语言使用 API ​​的信息。

标量值类型:double,float,int32,int64,uint32,uint64,sint32,sint64,fixed32,fixed64,sfixed32,sfixed64,bool,string,bytes


Optional 可选字段和默认值

如上所述,message 描述中的元素可以标记为可选 optional。格式良好的 message 可能包含也可能不包含被声明为可选的元素。解析 message 时,如果 message 不包含 optional 元素,则解析对象中的相应字段将设置为该字段的默认值。可以将默认值指定为 message 描述的一部分。例如,假设你要为 SearchRequest 的 result_per_page 字段提供默认值10。

optional int32 result_per_page = 3 [default = 10];

如果未为 optional 元素指定默认值,则使用特定于类型的默认值:对于字符串,默认值为空字符串。对于 bool,默认值为 false。对于数字类型,默认值为零。对于枚举,默认值是枚举类型定义中列出的第一个值。这意味着在将值添加到枚举值列表的开头时必须小心。


使用其他 Message 类型 & 嵌套类型

你可以使用其他 message 类型作为字段类型。例如,假设你希望在每个 SearchResponse 消息中包含 Result message - 为此,你可以在同一 .proto 中定义 Result message 类型,然后在SearchResponse 中指定 Result 类型的字段:

message SearchResponse {
  repeated Result result = 1;
}

message Result {
  required string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

你可以在其他 message 类型中定义和使用 message 类型,如下例所示 - 此处结果消息在SearchResponse 消息中定义:

message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

如果要在其父消息类型之外重用此消息类型,请将其称为 Parent.Type

message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

Oneof

如果你的 message 包含许多可选字段,并且最多只能同时设置其中一个字段,则可以使用 oneof 功能强制执行此行为并节省内存。

Oneof 字段类似于可选字段,除了 oneof 共享内存中的所有字段,并且最多只能同时设置一个字段。设置 oneof 的任何成员会自动清除所有其他成员。你可以使用特殊的 case() 或 WhichOneof() 方法检查 oneof 字段中当前是哪个值(如果有)被设置,具体方法取决于你选择的语言。

要在 .proto 中定义 oneof,请使用 oneof 关键字,后跟你的 oneof 名称,在本例中为 test_oneof:

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

然后,将 oneof 字段添加到 oneof 定义中。你可以添加任何类型的字段,但不能使用 requiredoptionalrepeated 关键字。如果需要向 oneof 添加重复字段,可以使用包含重复字段的 message。

Oneof 特性:

  1. 如果解析器遇到同一个 oneof 的多个成员,则在解析的消息中仅使用看到的最后一个成员。
  2. oneof 不支持扩展
  3. oneof 不能使用 repeated
  4. 反射 API 适用于 oneof 字段
  5. 如果你使用的是 C++,请确保你的代码不会导致内存崩溃。以下示例代码将崩溃,因为已通过调用 set_name() 方法删除了 sub_message。
  6. 设置 oneof 字段将自动清除 oneof 的所有其他成员。因此,如果你设置了多个字段,则只有你设置的最后一个字段仍然具有值。
  7. 同样在 C++中,如果你使用 Swap() 交换了两条 oneofs 消息,则每条消息将以另一条消息的 oneof 实例结束:在下面的示例中,msg1 将具有 sub_message 而 msg2 将具有 name。

生成你的类

要生成 Java,Python 或 C++代码,你需要使用 .proto 文件中定义的 message 类型,你需要在 .proto 上运行 protocol buffer 编译器 protoc。如果尚未安装编译器,请 下载软件包 并按照 README 文件中的说明进行操作。

Protocol 编译器的调用如下:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH 指定在解析导入指令时查找 .proto 文件的目录。如果省略,则使用当前目录。可以通过多次传递 --proto_path 选项来指定多个导入目录;他们将按顺序搜索。-I = IMPORT_PATH 可以用作 --proto_path 的缩写形式。
  • 你可以提供一个或多个输出指令:
    • --cpp_outDST_DIR 中生成 C++ 代码。有关详细信息,请参阅 C++ 生成的代码参考
    • --java_outDST_DIR中生成 Java 代码。有关更多信息,请参阅 Java 生成的代码参考
    • --python_outDST_DIR 中生成 Python 代码。有关更多信息,请参阅 Python 生成的代码
      为了方便起见,如果 DST_DIR 以 .zip 或 .jar 结尾,编译器会将输出写入到具有给定名称的单个 ZIP 格式的存档文件。.jar 输出还将根据 Java JAR 规范的要求提供清单文件。请注意,如果输出存档已存在,则会被覆盖;编译器不够智能,无法将文件添加到现有存档中。
  • 你必须提供一个或多个 .proto 文件作为输入。可以一次指定多个 .proto 文件。虽然文件是相对于当前目录命名的,但每个文件必须驻留在其中一个 IMPORT_PATH 中,以便编译器可以确定其规范名称。

参考:Protocol Buffers  |  Google Developers

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值