转载请注明原始链接:http://blog.csdn.net/a464057216/article/details/54896886
后续此博客不再更新,欢迎大家搜索关注微信公众号“测开之美”,测试开发工程师技术修炼小站,持续学习持续进步。
简介
Protocol Buffers(也叫Protobuf,简称PB)是Google的一种可扩展的用于将结构化数据序列化的技术,独立于平台、独立于语言,可以用于数据通信、存储等领域。同XML相比,PB更轻量、更快、更简单。定义好如何存储结构化数据以后,可以方便地通过编译产物从/向不同的数据流读/写数据,并且不受限于开发语言。目前PB支持C++、Java、Python,新版本的proto3还支持Java Lite、Ruby、Javascript、Objective-C、C#、go等,更多语言支持在逐渐加入进来。
官方仓库位于GitHub。使用PB需要安装PB编译器protoc和不同语言的PB运行时支持。protoc的下载可以也访问GitHub,或者从maven获取历史版本。
定义message类型
在.proto
文件中定义数据如何组织,一个message是一个逻辑上独立的信息记录,包含多个name-value对(name-value pair,也叫域field),官方建议message的名字使用CamelCase。比如:
message Person {
required string name = 1; //=1、=2是每个域在二进制编码中唯一的标识号
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;
}
每个域包含域规则、类型、名字和标识号等字段。
域规则
域规则包括required、optional、repeated:
-
required:格式良好的message应该有且只有一个required域。required域必须设置该域的值,否则message被认为是未初始化的,未初始化的message在序列化或解析时会抛异常。
-
optional:格式良好的message可以没有或只有一个optional域。optional域的值可以设置也可以不设置,如果未设置optional域的值,则采用默认值,默认值的设置放在标识号的后面,如果没有设置默认值,则采用系统默认规则:整型默认值是0,string类型默认值是空字符串,bool类型默认值是false,枚举类型的默认值是定义枚举时的第一个值,对于message类型,默认值是其所有域都没有被set的一个默认实例。
-
repeated:格式良好的message中repeated域可以重复存在任意次(包括零次),重复的值的顺序也会保留。由于历史原因,标量数值类型的repeated域没有被高效编码,新代码中用户应该使用[packed=true]保证更高效的编码,比如:
repeated int32 samples = 4 [packed=true];
域类型
类型可以是标量类型或者复合类型如message、enum等。
标量类型
.proto类型 | C++类型 | 备注 |
---|---|---|
double | double | |
float | float | |
int32 | int32 | 使用可变长编码方式。编码负数不够高效,如果字段可能含有负数,使用sint32。 |
int64 | int64 | 使用可变长编码方式。编码负数不够高效,如果字段可能含有负数,使用sint64。 |
uint32 | uint32 | 使用可变长编码方式。 |
uint64 | uint64 | 使用可变长编码方式。 |
sint32 | int32 | 使用可变长编码方式。有符号的整型值。 |
sint64 | int64 | 使用可变长编码方式。有符号的整型值。 |
fixed32 | uint32 | 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。 |
fixed64 | uint64 | 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。 |
sfixed32 | int32 | 总是4个字节。 |
sfixed64 | int64 | 总是8个字节。 |
bool | bool | |
string | string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 |
bytes | string | 可能包含任意顺序的字节数据。 |
复合类型
枚举类型
官方建议枚举的名字使用CamelCase,枚举中每个值的名字使用CAPITALS_WITH_UNDERSCORES形式,比如:
enum Foo{
FIRST_VALUE = 1;
SECOND_VALUE = 2;
}
值与值之间用分号分隔。一般情况下,不允许为不同的字面量定义相同的值,在枚举定义中开启了allow_alias开关时才允许有别名,比如:
enum EnumAllowAlias {
option allow_alias = true;
UNKOWN = 0;
STARTED = 1;
BEGIN = 1;
}
枚举值的取值范围不能超过32bit,由于内部使用变长编码,考虑到效率不推荐枚举存储负数。枚举可以定义在message中,或message外(此时该枚举可以被同一个.proto
文件中的其他message使用)。如果枚举定义在message中,同一个.proto文件中其他message想要使用该枚举类型时,可以使用MessageType.EnumType
的语法。
message类型
使用message类型用来定义嵌套的message结构,比如:
message SearchResponse {
repeated Result result = 1;
}
message Result {
required string url = 1;
}
message也可以嵌套定义,并且可以嵌套任意层:
message SearchResponse {
repeated Result result = 1;
message Result {
required string url = 1;
}
}
域名字
官方建议域的名字使用小写字母且下划线分隔的形式。
标识号
推荐为频繁使用的域使用 [1,15]之内的标识号(注意为将来可能常用的域预留标识号)。不可使用[19000-19999]的标识号(Protobuf预留)。可用标识号最大到2^29 - 1即536870911。一个标识号一旦被占用,在未来升级message的时候,这个标识号永远都不能再被使用了,可以使用reserved声明表示标识号不能再被使用(比如某些域被删除的情形):
message Foo {
reserved 2, 15, 9 to 11; //标识号可以使用a to b的形式,左闭右闭
reserved "foo", "bar"; //字符串表示域名
reserved 14, "xx"; //可以混合域名和标识号
}
导入定义
如果想引用其他.proto
文件中定义的message,可以在.proto
文件开头使用import语句(以分号结尾)。import不会做递归处理即只能使用直接import的.proto
文件中的定义。
如果被import的.proto
文件移动到了新的位置,可以在旧位置编辑一个哑.proto
文件,该哑.proto
文件通过import public将外层import请求转到新的位置,比如:
//new.proto ——定义被移动到了这
//old.proto ——哑.proto文件
import public "new.proto"
import "other.proto"
//client.proto
import "old.proto"
按照上面的组织结构,client.proto中可以使用old.proto和new.proto中的定义,但是不能使用other.proto中的定义。
最后,一个.proto
文件中可以定义多个相关联的message。.proto
文件采用C++风格的单行注释(//)。
使用PB编译器编译
protoc编译器可以根据.proto
文件生成不同语言的代码,这些代码包含获取和设置message各个域的方法、将message序列化到输出流的方法、从输出流解析出message的方法等。
- C++:每个
.proto
文件生成一个.h
和.cc
文件,每个message对应一个类的定义。 - Python:根据
.proto
文件生成一个模块(.py
文件),该模块包含每个message对应的静态描述符,在运行时通过metaclass
生成必要的Python数据访问类。
比如生成C++的数据访问类使用如下命令:
protoc --cpp_out=. Person.proto
生成Python的数据访问类使用如下命令:
protoc --python_out=. Person.proto
也可以使用-IPATH, --proto_path=PATH
参数设置import语句的搜索路径,如果指定了多个路径,则按照给定路径顺序寻找,如果没有设置-I
参数,默认从protoc命令执行目录搜索。
如果觉得我的文章对您有帮助,欢迎关注我(CSDN:Mars Loo的博客)或者为这篇文章点赞,谢谢!