一、简介。
protobuf是由Google开发的一套对数据结构进行序列化的方法,可用做通信协议,数据存储格式,等等。其特点是不限语言、不限平台、扩展性强,就像XML一样。与XML相比,protobuf有以下特点:
1、操作更简单。
例如,我们要定义一个个人信息的结构,其中包括名称和邮箱地址两个部分。
用XML定义如下:
<person>
<name>John Doe</name>
<email>jdoe@example.com</email>
</person>
使用C++,访问方式可能如下:
cout << person.getTag("name").getText() << endl;
cout << person.getTag("email").getText() << endl;
如果用protobuf,则定义如下
message person {
optional string name;
optional string email;
}
如果使用C++,访问方式可能如下:
cout << person.getName() << endl;
cout << person.getEmail() << endl;
访问方式更简单。
2、序列化后生成的代码体积更小
还是上述消息,使用protobuf进行编码后,体积大约是28个字节;而如果使用XML进行编码,则大约需要69个字节(除去空格)。
3、解析速度更快。
对上述编码后的数据进行解析,protobuf需要100-200纳秒;而XML则需要5000-10000纳秒。
4、与XML相比,protobuf的缺点是不易读。众所周知,XML是一种自描述语言,一看就可知道其作用,见文知意。而protobuf序列化后是一串二进制代码,如果没有对应的协议格式(即.proto文件),想要读懂它难如登天。另外,如果目标是一种基于文版的标签式文档(如html),则XML更具优势。
关于protobuf的历史及其特点,可参考Google网站:点击打开链接
二、语法规则
protobuf协议的文件后缀名为.proto。一个简单的protobuf协议如下:
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 AddressBook {
repeated Person person = 1;
}
1、标识符
protobuf协议的标识符为message或enum,如示例中的Person和PhoneType。message标识一条消息,enum标识一个枚举类型。使用protobuf编译器将协议文件编译后,message和enum都会生成一个类。
2、字段
协议字段格式如下:
role type name = tag [default value];
role有三种取值:
required:表示该字段必须有值,不能为空,否则message被认为是未初始化的。如果试图build一个未初始化的message将会抛出RuntimeException。解析未初始化的message会抛出IOException。
optional:表示该段为可选值,可以不进行设置。如果不设置,会设置一个默认值。可以设置一个默认值,正如示例中的type字段。否则使用系统默认值,数字类型默认为0;字符串类型默认为空字符串;逻辑类型默认为false;内部自定义的message,默认值通常是message的实例或原型。
repeated:表示该字段可以重复,可等同于动态数组。
注意:使用required字段一定要小心,因为该字段是永久性的。如果以后因为某种原因,想不用该字段,或者要将该字段改成optional或者repeated,那么使用旧接口读取新的协议时,如果发现没有该字段,他们会认为该字段是不完整的,会拒接接收该消息,或者直接丢弃。
3、字段类型
protobuf 数据类型 | 描述 | 打包 | C++语言映射 |
---|---|---|---|
bool | 布尔类型 | 1字节 | bool |
double | 64位浮点数 | N | double |
float | 32为浮点数 | N | float |
int32 | 32位整数、 | N | int |
uin32 | 无符号32位整数 | N | unsigned int |
int64 | 64位整数 | N | __int64 |
uint64 | 64为无符号整 | N | unsigned __int64 |
sint32 | 32位整数,处理负数效率更高 | N | int32 |
sing64 | 64位整数 处理负数效率更高 | N | __int64 |
fixed32 | 32位无符号整数 | 4 | unsigned int32 |
fixed64 | 64位无符号整数 | 8 | unsigned __int64 |
sfixed32 | 32位整数、能以更高的效率处理负数 | 4 | unsigned int32 |
sfixed64 | 64为整数 | 8 | unsigned __int64 |
string | 只能处理 ASCII字符 | N | std::string |
bytes | 用于处理多字节的语言字符、如中文 | N | std::string |
enum | 可以包含一个用户自定义的枚举类型uint32 | N(uint32) | enum |
message | 可以包含一个用户自定义的消息类型 | N | object of class |
N 表示打包的字节并不是固定。而是根据数据的大小或者长度。
例如int32,如果数值比较小,在0~127时,使用一个字节打包。
关于枚举的打包方式和uint32相同。
关于message,类似于C语言中的结构包含另外一个结构作为数据成员一样。
关于 fixed32 和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.