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的安装使用的更多相关文章

  1. PHP7中Protobuf的安装使用

    PHP7中Protobuf的安装使用 写这篇文章的缘由是最近在关注RPC框架序列化的一些原理.但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不 ...

  2. Protobuf从安装到配置整理帖

    新做的Mini项目计划使用Google的Protobuf来做,关于Protobuf是什么玩意能干什么请自己去看这里:http://code.google.com/p/protobuf/ 这里讲一下安装 ...

  3. protobuf的安装和使用

    以下全部基于win7系统. protobuf是什么,有什么用网上说的已经很多了.这里就是说一下怎么使用.就当给自己做个笔记吧. .proto文件的语法什么的也请网上查看,挺多的. 第一步: 下载pro ...

  4. Google protobuf的安装及使用

    最近应为工作的需要,合作的部门提供了protobuf的接口,总结了一下使用的过程和方法如下: 下载protobuf-2.3.0: http://protobuf.googlecode.com/file ...

  5. Protobuf从安装到配置整理帖 --转

    新做的Mini项目计划使用Google的Protobuf来做,关于Protobuf是什么玩意能干什么请自己去看这里:http://code.google.com/p/protobuf/ 这里讲一下安装 ...

  6. protobuf 编译安装

    1.protobuf是google公司提出的数据存储格式,详细介绍可以参考:https://developers.google.com/protocol-buffers 2.下载最新的protobuf ...

  7. protobuf编译安装

    为什么选择protobuf,而不选择thift和avro,原因大概几点吧,网上对比的文章很多,我主要关注以下几点 1.protobuf序列化性能最好,序列化后字节数最少. 2.protobuf是单纯的 ...

  8. C++ProtoBuf的安装与使用

    目录 安装(Ubuntu 16.04) 简介 proto2 proto3 用法 proto3 输出结果 总结 @(目录) 安装(Ubuntu 16.04) sudo apt-get install a ...

  9. protobuf文档翻译-安装,数据格式及编码规范

    Install Download protobuf: https://github.com/protocolbuffers/protobuf/releases unzip protoc-3.8.0-l ...

随机推荐

  1. 谈谈Android中的Rect类——奇葩的思维

    最近在工作中遇到了一些问题,总结下来就是Android中Rect这个类造成的.不得不说,不知道Android SDK的开发人员是怎么想的, 这个类设计的太奇葩了.首先介绍一下Rect类:Rect类主要 ...

  2. Java编程思想学习笔记——字符串

    前言 字符串操作是计算机程序设计中最常见的行为. 不可变String String对象是不可变的 重载"+"与StringBuilder String对象是不可变的,可以给Stri ...

  3. Win7下telnet使用

    出于安全考虑,win7已经禁用了telnet这一功能, telnet是明文传输的,安全性很差. 知道了这一点就不奇怪为什么在win7下不能使用telnet了,下面就详细介绍下如何重新开启telnet服 ...

  4. [Module] 06 - DataBinding and MVVM

    下一步学习列表: Android DataBinding使用总结(一) *** Android DataBinding使用总结(二) Android DataBinding使用总结(三)列表展示 An ...

  5. SQL SERVER发布与订阅

    一.配置分发 1.配置分发服务器,注:配置发布与订阅,连接SQLSERVER必须用服务器名登录 2.配置分发 3.选择分发服务器 4.选择快照文件夹 5.设置此文件夹的读写权限为everyone 6. ...

  6. POJ 3258 River Hopscotch(二分法搜索)

    Description Every year the cows hold an event featuring a peculiar version of hopscotch that involve ...

  7. ios开发之--首页 导航栏隐藏 下一级页面显示,pop回来显示白条

    解决方法,在首页中实现如下两个方法,代码如下: -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated] ...

  8. php mongodb manager 查数据的各种姿势

    一.连接 mongodb 数据库 以下操作默认都是以上面操作为前提(已连接成功)而做的操作 二.查的各种姿势 1)通过主键_id来查询,注意:_id不是直接用字符串来表示,要用 new \MongoD ...

  9. 【代码审计】iZhanCMS_v2.1 代码执行漏洞分析

      0x00 环境准备 iZhanCMS官网:http://www.izhancms.com 网站源码版本:爱站CMS(zend6.0) V2.1 程序源码下载:http://www.izhancms ...

  10. 关于 Handler 与 opener

    我们可以使用 urllib.request.Request() 构造请求对象,但是对于一些更高级的操作,比如 Cookies 处理.代理设置 .身份验证等等,Request() 是处理不了的这时就需要 ...