Protocol Buffer语法规范

Google在Proto2的基础上发布了Proto3,并且Proto3和Proto2在语法规范上存在一定的差异(据说是更简单了)。本文只针对Proto2的语法规范进行学习和总结,对Proto3有兴趣的同学可参考文末第一个参考链接。
1.message类型

前一篇文章我们已经接触过一个简单的.proto文件了,在文件中我们定义了一个名为Person的message类型,并且在Person的内部,我们还嵌套的定义了其他的message类型。此处,我们再定义一个message类型用来满足某种查询请求的场景,其中每个查询请求都有一个“查询字符串”、“感兴趣的目标页面”以及“每个页面的许多结果”。相应地,我们可以在.proto文件中这样定义一个message类型:

(1)字段类型

如上示例,使用了两个整型int32类型以及一个字符串string类型。除此之外,您还可以根据自己的需要使用复合类型,如枚举类型、message类型等。

(2)字段编号

SearchRequest类型中的三个字段被分别指定了三个不同的编号,这些编号是用来标识二进制格式中的各个字段的,所以不要轻易修改哦。

其次,编号1-15需要一个字节进行编码,编码16-2017则需要两个字节。所以,您应该将1-15用于那些需要频繁使用的字段上。

最后,最小的字段编号为1,最大的字段编号为2^29-1,即536870911。19000-1999为保留字段,不要使用。

(3)字段限定符

有如下三个字段限定符:

1)required:该限定符修饰的字段,表示一个符合语法规则的消息(well-formed message)必须具有此字段;
2)optional:该限定符修饰的字段,表示一个符合语法规则的消息可以有0个或不超过1个字段;
3)repeated:该限定符修饰的字段,表示一个符合语法规则的消息中,此字段可以重复多次(包括零次)。

由于历史原因,repeated修饰的数值类型字段没有得到高效的编码,所以,新代码应该使用一个特殊的选项——[packed=true]来获得高效的编码效果:

注:更多有关Protocol Buffer编码相关的内容,请参考https://developers.google.com/protocol-buffers/docs/encoding.html#packed

(4)其他

学习了以上三点之后,编写一个message类型对你来说应该就没有问题了。此外,还有几点需要提及的是:
1)在一个.proto文件中可以定义多个message类型;
2)编写.proto文件时,我们可以使用C/C++中的那一套注释风格来帮助我们注释,如//或/*…*/;
3)保留字段。
使用reserved关键字可以保留已删除字段和字段编号,如果将来用户尝试使用这些字段标识符,protobuf编译器将会报错,避免导致其他严重的问题:

2.基本类型

protobuf具有以下若干种基本类型,并且这些类型在相应的语言中的一一对应关系如下图2-1所示:type

图2-1

3.可选字段和默认值

如上所述,message类型中的字段可以标记为optional。在解析消息时,如果该消息不包含可选的字段内容,则解析对象中的相应字段将被设置为该字段的默认值。比如,我们可以指定result_per_page的默认值为10:

如果没有显式地为一个optional字段指定默认值,那么相应的optional字段将使用该字段类型的默认值。比如,string类型的默认值为空;bool类型的默认值为false;数值类型的默认值为0;枚举类型的默认值是枚举类型定义中列出的第一个值。

4.枚举类型

protobuf复合类型中提供了枚举类型,类似于C/C++中的枚举类型(不同点在于,C/C++中枚举类型中的字段是以逗号分隔的,protobuf则是以分号分隔)。你可以这样使用:

此外,在message类型中介绍的reserved规则同样适用于枚举类型。当你更新或删除enum中的条目时,记得将其置为reserved,避免产生严重的灾难后果:

5.使用其他message

前面也有说过,我们可以在一个message类型中使用另一个message类型:

(1)导入其他.proto文件

上面的示例中,SearchResponse类型中引用了Result类型,并且二者都定义在一个.proto文件中。假设我们需要引入的message类型已经定义在了其他某个.proto文件中,该怎么办呢?

使用import导入相应.proto文件即可:

(2)使用proto3的消息类型

值得一提的是,在proto2的语法规则下,我们可以导入及使用在proto3中定义的message类型,反之亦然。然而有一点例外的是,proto2的枚举类型不能在proto3中使用。

6.嵌套类型

Protobuf还允许我们在message类型中再定义其他的message,比如前面定义的SearchResponse等价定义:

7.更新message

永远不会变化的就是变化了,假如现在的message类型已经满足不了你的需求了,你希望在现有的message中添加一个额外的字段,同时你仍然希望使用旧的message所生成的代码。那么,在不破坏现有代码的情况下更新message类型也是非常简单的,你只需记住以下规则:

(1)不要试图改变已有字段的编号;
(2)新增字段的限定符必须为optional或repeated,而决不能是required;
(3)可以删除非required修饰的字段,前提是该字段的编码不要在你更新的message类型中再次使用;
(4)int32, uint32, int64, uint64以及bool类型都是互相兼容的。这意味着您可以将这些类型更改为其中的另一种类型;
(5)sint32和sint64是互相兼容的,但不兼容与其他的数值类型;
(6)如果字节是utf8编码的,那么string和bytes是互相兼容的;
(7)fixed32和sfixed32兼容,fixed64和sfixed64兼容;
(8)optional与repeated是兼容的。
(9)…

8.Package

为了防止协议消息类型之间的名称冲突,Protobuf引入了package特性,你可以在.proto文件中添加一条package语句(该语句是可选的):

如果你生成的是C++源文件,那么这里的package的作用就类似于C++中的命名空间namespace,如果你生成的是Java源文件,那么这里的package的作用就类似于Java中的package。

9.Protobuf编译器

要生成C++,Java或Python等代码,您需要使用Protobuf的编译器protoc去编译.proto文件。一般使用方法如下:

其中,
1)IMPORT_PATH指定在解析import指令时查找proto文件的目录,如果省略则使用当前目录,缩写形式为-I=IMPORT_PATH;
2)–cpp_out表示在DST_DIR中生成C++代码,类似的有–java_out, –python_out等;
3)此外,如果DST_DIR是以.zip或.jar结尾的,编译器将把输出写入相应的归档文件中。如果输出的文件已经存在,将会覆盖原有文件。

10.其他

现在我们对Protocol Buffer的主要特性已经有了较清晰的认知了。除此之外,还有部分高级特性未做介绍(如extensions, maps, oneof,options),有兴趣的同学请参考文末参考链接。

 

参考:
[1]. https://developers.google.com/protocol-buffers/docs/proto3
[2]. https://developers.google.com/protocol-buffers/docs/proto

发表评论

电子邮件地址不会被公开。 必填项已用*标注