Protobuf的安装使用
date: 2018-10-12 18:59:13
版权归属原作者,本位转自:https://www.cnblogs.com/autyinjing/p/6495103.html
1. 是什么?
Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。
2. 为什么要用?
- 平台无关,语言无关,可扩展;
- 提供了友好的动态库,使用简单;
- 解析速度快,比对应的XML快约20-100倍;
- 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。
3. 怎么安装?
源码下载地址: https://github.com/google/protobuf
安装依赖的库: autoconf automake libtool curl make g++ unzip
安装:
1 $ ./autogen.sh
2 $ ./configure
3 $ make
4 $ make check
5 $ sudo make install
二、怎么用?
1. 编写proto文件
首先需要一个proto文件,其中定义了我们程序中需要处理的结构化数据:

1 // Filename: addressbook.proto
2
3 syntax="proto2";
4 package addressbook;
5
6 import "src/help.proto"; //举例用,编译时去掉
7
8 message Person {
9 required string name = 1;
10 required int32 id = 2;
11 optional string email = 3;
12
13 enum PhoneType {
14 MOBILE = 0;
15 HOME = 1;
16 WORK = 2;
17 }
18
19 message PhoneNumber {
20 required string number = 1;
21 optional PhoneType type = 2 [default = HOME];
22 }
23
24 repeated PhoneNumber phone = 4;
25 }
26
27 message AddressBook {
28 repeated Person person_info = 1;
29 }

2. 代码解释
// Filename: addressbook.proto 这一行是注释,语法类似于C++
syntax="proto2"; 表明使用protobuf的编译器版本为v2,目前最新的版本为v3
package addressbook; 声明了一个包名,用来防止不同的消息类型命名冲突,类似于 namespace
import "src/help.proto"; 导入了一个外部proto文件中的定义,类似于C++中的 include 。不过好像只能import当前目录及当前目录的子目录中的proto文件,比如import父目录中的文件时编译会报错(Import
"../xxxx.proto" was not found or had errors.),使用绝对路径也不行,尚不清楚原因,官方文档说使用 -I=PATH 或者 --proto_path=PATH 来指定import目录,但实际实验结果表明这两种方式指定的是将要编译的proto文件所在的目录,而不是import的文件所在的目录。(哪位大神若清楚还请不吝赐教!)
message 是Protobuf中的结构化数据,类似于C++中的类,可以在其中定义需要处理的数据
required string name = 1; 声明了一个名为name,数据类型为string的required字段,字段的标识号为1
protobuf一共有三个字段修饰符:
- required:该值是必须要设置的;
- optional :该字段可以有0个或1个值(不超过1个);
- repeated:该字段可以重复任意多次(包括0次),类似于C++中的list;
使用建议:除非确定某个字段一定会被设值,否则使用optional代替required。
string 是一种标量类型,protobuf的所有标量类型请参考文末的标量类型列表。
name 是字段名,1 是字段的标识号,在消息定义中,每个字段都有唯一的一个数字标识号,这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。
标识号的范围在:1 ~ 229 - 1,其中[19000-19999]为Protobuf预留,不能使用。
Person 内部声明了一个enum和一个message,这类似于C++中的类内声明,Person外部的结构可以用 Person.PhoneType 的方式来使用PhoneType。当使用外部package中的结构时,要使用 pkgName.msgName.typeName 的格式,每两层之间使用'.'来连接,类似C++中的"::"。
optional PhoneType type = 2 [default = HOME]; 为type字段指定了一个默认值,当没有为type设值时,其值为HOME。
另外,一个proto文件中可以声明多个message,在编译的时候他们会被编译成为不同的类。
3. 生成C++文件
protoc是proto文件的编译器,目前可以将proto文件编译成C++、Java、Python三种代码文件,编译格式如下:
1 protoc -I=$SRC_DIR --cpp_out=$DST_DIR /path/to/file.proto
上面的命令会生成xxx.pb.h 和 xxx.pb.cc两个C++文件。
4. 使用C++文件
现在编写一个main.cc文件:

1 #include <iostream>
2 #include "addressbook.pb.h"
3
4 int main(int argc, const char* argv[])
5 {
6 addressbook::AddressBook person;
7 addressbook::Person* pi = person.add_person_info();
8
9 pi->set_name("aut");
10 pi->set_id(1219);
11 std::cout << "before clear(), id = " << pi->id() << std::endl;
12 pi->clear_id();
13 std::cout << "after clear(), id = " << pi->id() << std::endl;
14 pi->set_id(1087);
15 if (!pi->has_email())
16 pi->set_email("autyinjing@126.com");
17
18 addressbook::Person::PhoneNumber* pn = pi->add_phone();
19 pn->set_number("021-8888-8888");
20 pn = pi->add_phone();
21 pn->set_number("138-8888-8888");
22 pn->set_type(addressbook::Person::MOBILE);
23
24 uint32_t size = person.ByteSize();
25 unsigned char byteArray[size];
26 person.SerializeToArray(byteArray, size);
27
28 addressbook::AddressBook help_person;
29 help_person.ParseFromArray(byteArray, size);
30 addressbook::Person help_pi = help_person.person_info(0);
31
32 std::cout << "*****************************" << std::endl;
33 std::cout << "id: " << help_pi.id() << std::endl;
34 std::cout << "name: " << help_pi.name() << std::endl;
35 std::cout << "email: " << help_pi.email() << std::endl;
36
37 for (int i = 0; i < help_pi.phone_size(); ++i)
38 {
39 auto help_pn = help_pi.mutable_phone(i);
40 std::cout << "phone_type: " << help_pn->type() << std::endl;
41 std::cout << "phone_number: " << help_pn->number() << std::endl;
42 }
43 std::cout << "*****************************" << std::endl;
44
45 return 0;
46 }

5. 常用API
protoc为message的每个required字段和optional字段都定义了以下几个函数(不限于这几个):
1 TypeName xxx() const; //获取字段的值
2 bool has_xxx(); //判断是否设值
3 void set_xxx(const TypeName&); //设值
4 void clear_xxx(); //使其变为默认值
为每个repeated字段定义了以下几个:
1 TypeName* add_xxx(); //增加结点
2 TypeName xxx(int) const; //获取指定序号的结点,类似于C++的"[]"运算符
3 TypeName* mutable_xxx(int); //类似于上一个,但是获取的是指针
4 int xxx_size(); //获取结点的数量
另外,下面几个是常用的序列化函数:
1 bool SerializeToOstream(std::ostream * output) const; //输出到输出流中
2 bool SerializeToString(string * output) const; //输出到string
3 bool SerializeToArray(void * data, int size) const; //输出到字节流
与之对应的反序列化函数:
1 bool ParseFromIstream(std::istream * input); //从输入流解析
2 bool ParseFromString(const string & data); //从string解析
3 bool ParseFromArray(const void * data, int size); //从字节流解析
其他常用的函数:
1 bool IsInitialized(); //检查是否所有required字段都被设值
2 size_t ByteSize() const; //获取二进制字节序的大小
官方API文档地址: https://developers.google.com/protocol-buffers/docs/reference/overview
6. 编译生成可执行代码
编译格式和普通的C++代码一样,但是要加上 -lprotobuf -pthread
1 g++ main.cc xxx.pb.cc -I $INCLUDE_PATH -L $LIB_PATH -lprotobuf -pthread
7. 输出结果

1 before clear(), id = 1219
2 after clear(), id = 0
3 *****************************
4 id: 1087
5 name: aut
6 email: autyinjing@126.com
7 phone_type: 1
8 phone_number: 021-8888-8888
9 phone_type: 0
10 phone_number: 138-8888-8888
11 *****************************

三、怎么编码的?
protobuf之所以小且快,就是因为使用变长的编码规则,只保存有用的信息,节省了大量空间。
1. Base-128变长编码
- 每个字节使用低7位表示数字,除了最后一个字节,其他字节的最高位都设置为1;
- 采用Little-Endian字节序。
示例:

1 -数字1:
2 0000 0001
3
4 -数字300:
5 1010 1100 0000 0010
6 000 0010 010 1100
7 -> 000 0010 010 1100
8 -> 100101100
9 -> 256 + 32 + 8 + 4 =

2. ZigZag编码
Base-128变长编码会去掉整数前面那些没用的0,只保留低位的有效位,然而负数的补码表示有很多的1,所以protobuf先用ZigZag编码将所有的数值映射为无符号数,然后使用Base-128编码,ZigZag的编码规则如下:
1 (n << 1) ^ (n >> 31) or (n << 1) ^ (n >> 63)
负数右移后高位全变成1,再与左移一位后的值进行异或,就把高位那些无用的1全部变成0了,巧妙!
3. 消息格式
每一个Protocol Buffers的Message包含一系列的字段(key/value),每个字段由字段头(key)和字段体(value)组成,字段头由一个变长32位整数表示,字段体由具体的数据结构和数据类型决定。
字段头格式:
1 (field_number << 3) | wire_type
2 -field_number:字段序号
3 -wire_type:字段编码类型
4. 字段编码类型
Type | Meaning | Used For |
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages(嵌套message), packed repeated fields |
3 | Start group | groups (废弃) |
4 | End group | groups (废弃) |
5 | 32-bit | fixed32, sfixed32, float |
5. 编码示例(下面的编码以16进制表示)

1 示例1(整数)
2 message Test1 {
3 required int32 a = 1;
4 }
5 a = 150 时编码如下
6 08 96 01
7 08: 1 << 3 | 0
8 96 01:
9 1001 0110 0000 0001
10 -> 001 0110 000 0001
11 -> 1001 0110
12 -> 150
13
14 示例2(字符串)
15 message Test2 {
16 required string b = 2;
17 }
18 b = "testing" 时编码如下
19 12 07 74 65 73 74 69 6e 67
20 12: 2 << 3 | 2
21 07: 字符串长度
22 74 65 73 74 69 6e 67
23 -> t e s t i n g
24
25 示例3(嵌套)
26 message Test3 {
27 required Test1 c = 3;
28 }
29 c.a = 150 时编码如下
30 1a 03 08 96 01
31 1a: 3 << 3 | 2
32 03: 嵌套结构长度
33 08 96 01
34 ->Test1 { a = 150 }
35
36 示例4(可选字段)
37 message Test4 {
38 required int32 a = 1;
39 optional string b = 2;
40 }
41 a = 150, b不设值时编码如下
42 08 96 01
43 -> { a = 150 }
44
45 a = 150, b = "aut" 时编码如下
46 08 96 01 12 03 61 75 74
47 08 96 01 -> { a = 150 }
48 12: 2 << 3 | 2
49 03: 字符串长度
50 61 75 74
51 -> a u t
52
53 示例5(重复字段)
54 message Test5 {
55 required int32 a = 1;
56 repeated string b = 2;
57 }
58 a = 150, b = {"aut", "honey"} 时编码如下
59 08 96 01 12 03 61 75 74 12 05 68 6f 6e 65 79
60 08 96 01 -> { a = 150 }
61 12: 2 << 3 | 2
62 03: strlen("aut")
63 61 75 74 -> a u t
64 12: 2 << 3 | 2
65 05: strlen("honey")
66 68 6f 6e 65 79 -> h o n e y
67
68 a = 150, b = "aut" 时编码如下
69 08 96 01 12 03 61 75 74
70 08 96 01 -> { a = 150 }
71 12: 2 << 3 | 2
72 03: strlen("aut")
73 61 75 74 -> a u t
74
75 示例6(字段顺序)
76 message Test6 {
77 required int32 a = 1;
78 required string b = 2;
79 }
80 a = 150, b = "aut" 时,无论a和b谁的声明在前面,编码都如下
81 08 96 01 12 03 61 75 74
82 08 96 01 -> { a = 150 }
83 12 03 61 75 74 -> { b = "aut" }

四、还有什么?
1. 编码风格
- 花括号的使用(参考上面的proto文件)
- 数据类型使用驼峰命名法:AddressBook, PhoneType
- 字段名小写并使用下划线连接:person_info, email_addr
- 枚举量使用大写并用下划线连接:FIRST_VALUE, SECOND_VALUE
2. 适用场景
"Protocol Buffers are not designed to
handle large
messages."。protobuf对于1M以下的message有很高的效率,但是当message是大于1M的大块数据时,protobuf的表现不是很好,请合理使用。
总结:本文介绍了protobuf的基本使用方法和编码规则,还有很多内容尚未涉及,比如:反射机制、扩展、Oneof、RPC等等,更多内容需参考官方文档。
标量类型列表
proto类型 | C++类型 | 备注 |
double | double | |
float | float | |
int32 | int32 | 使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint32 |
int64 | int64 | 使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint64 |
uint32 | uint32 | 使用可变长编码 |
uint64 | uint64 | 使用可变长编码 |
sint32 | int32 | 使用可变长编码,有符号的整型值,编码时比通常的int32高效 |
sint64 | int64 | 使用可变长编码,有符号的整型值,编码时比通常的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 | 可能包含任意顺序的字节数据 |
转载烦请注明转自: ———— http://www.cnblogs.com/autyinjing/ ———— 若发现叙述不清或者有误的,还烦请提出建议和意见!
Protobuf的安装使用的更多相关文章
- PHP7中Protobuf的安装使用
PHP7中Protobuf的安装使用 写这篇文章的缘由是最近在关注RPC框架序列化的一些原理.但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不 ...
- Protobuf从安装到配置整理帖
新做的Mini项目计划使用Google的Protobuf来做,关于Protobuf是什么玩意能干什么请自己去看这里:http://code.google.com/p/protobuf/ 这里讲一下安装 ...
- protobuf的安装和使用
以下全部基于win7系统. protobuf是什么,有什么用网上说的已经很多了.这里就是说一下怎么使用.就当给自己做个笔记吧. .proto文件的语法什么的也请网上查看,挺多的. 第一步: 下载pro ...
- Google protobuf的安装及使用
最近应为工作的需要,合作的部门提供了protobuf的接口,总结了一下使用的过程和方法如下: 下载protobuf-2.3.0: http://protobuf.googlecode.com/file ...
- Protobuf从安装到配置整理帖 --转
新做的Mini项目计划使用Google的Protobuf来做,关于Protobuf是什么玩意能干什么请自己去看这里:http://code.google.com/p/protobuf/ 这里讲一下安装 ...
- protobuf 编译安装
1.protobuf是google公司提出的数据存储格式,详细介绍可以参考:https://developers.google.com/protocol-buffers 2.下载最新的protobuf ...
- protobuf编译安装
为什么选择protobuf,而不选择thift和avro,原因大概几点吧,网上对比的文章很多,我主要关注以下几点 1.protobuf序列化性能最好,序列化后字节数最少. 2.protobuf是单纯的 ...
- C++ProtoBuf的安装与使用
目录 安装(Ubuntu 16.04) 简介 proto2 proto3 用法 proto3 输出结果 总结 @(目录) 安装(Ubuntu 16.04) sudo apt-get install a ...
- protobuf文档翻译-安装,数据格式及编码规范
Install Download protobuf: https://github.com/protocolbuffers/protobuf/releases unzip protoc-3.8.0-l ...
随机推荐
- memcached系列之
Slab Allocator的机制分配.管理内存 slabs---->slabs class:chunk size------>申请内存后分配的规格. chunk-->存放记录的单位 ...
- 安卓开发笔记——Menu菜单组件(选项菜单,上下文菜单,子菜单)
菜单是用户界面中最常见的元素之一,使用非常频繁,在Android中,菜单被分为如下三种,选项菜单(OptionsMenu).上下文菜单(ContextMenu)和子菜单(SubMenu). 菜单的实现 ...
- php 批量过滤关键字
代码: public function filterComment($content){ $string = "正品.真品.最.史上 .唯一.一流.独一无二.仅.独家.首.冠军. 国家级.领 ...
- Javascript数组与类数组对象
问题来由: 之前面试X公司的时候,问我关于原型链知道多少,于是我就开始说了一些有关于原型链的东西,面试官问Array的原型是什么,我说是对象,毕竟Array.prototype===array.__p ...
- greenplum的用法
gp建表的实例 gp 创建外部表的实例:(外部表不能建立分布键) CREATE EXTERNAL TABLE user_app_tag ( ...
- 视觉单词模型、词袋模型BoW
多用于图像检索.分类 3.2.1.4 视觉单词模型 视觉词袋(BoVW,Bag of Visual Words)模型,是“词袋”(BoW,Bag of Words)模型从自然语言处理与分析领域向图像处 ...
- jQuery easyUI的datagrid,如何在翻页以后仍能记录被选中的行
1.先给出问题解决后的代码 <%@ page language="java" import="java.util.*" pageEncoding=&quo ...
- linux环境中,查看域名的DNS信息?
需求说明: 今天在linux主机上,要查询一个域名是在哪个DNS上进行解析的,这个域名下面还有哪些的地址 操作过程: 1.linux环境中通过nslookup命令来进行查看 [deployer@CBS ...
- webdriver+expected_conditions二次封装
结合这两种方法对代码做二次封装,可以提升脚本性能 例: #coding:utf-8 #封装元素方法from selenium import webdriverfrom selenium.webdriv ...
- java之 ------ 可变參数和卫条件
可变參数:适用于參数个数不确定.类型确定的情况,java把可变參数当做数组处理. 可变參数必须位于最后一项.当可变參数个数多于一个时,必将有一个不是最后一项,所以仅仅支持有一个可变參数. 可变參数的书 ...