从公司的项目源码中看到了这个东西,觉得挺好用的,写篇博客做下小总结。下面的操作以C++为编程语言,protoc的版本为libprotoc 3.2.0。

一、Protobuf? 
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.sh2 $ ./configure3$ make4$ make check5 $ 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     enumPhoneType {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三种代码文件,编译格式如下:

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 charbyteArray[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字段都定义了以下几个函数(不限于这几个):

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: aut6 email: autyinjing@126.com7 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 = 300

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 g24
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 122 << 3 | 2
49 03: 字符串长度50 61 75 74
51 ->a u t52
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 122 << 3 | 2
62 03: strlen("aut")63 61 75 74 ->a u t64 122 << 3 | 2
65 05: strlen("honey")66 68 6f 6e 65 79 ->h o n e y67
68 a = 150, b = "aut"时编码如下69 08 96 01 12 03 61 75 74
70 08 96 01 -> { a = 150}71 122 << 3 | 2
72 03: strlen("aut")73 61 75 74 ->a u t74
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 可能包含任意顺序的字节数据

参考资料 
1. Protocol Buffers Developer Guide

2. Google Protocol Buffer 的使用和原理

3. 浅谈几种序列化协议

4. 序列化和反序列化

5. Protobuf使用手册

转自:https://www.cnblogs.com/autyinjing/p/6495103.html

转载于:https://www.cnblogs.com/zl1991/p/8696111.html

Protobuf学习 - 入门(转)相关推荐

  1. Protobuf学习入门(一)

      笔者最近在学习使用tensorflow/serving,其中有不少涉及Protobuf相关的内容,因此接触学习了Prorobuf,记录于此,希望能对读者有所启发.   本文作为Protobuf入门 ...

  2. 【AI参赛经验】深度学习入门指南:从零开始TinyMind汉字书法识别——by:Link

    各位人工智能爱好者,大家好! 由TinyMind发起的#第一届汉字书法识别挑战赛#正在火热进行中,比赛才开始3周,已有数只黑马冲进榜单.目前TOP54全部为90分以上!可谓竞争激烈,高手如林.不是比赛 ...

  3. 深度学习入门,一文讲解神经网络的构成、训练和算法

    小白深度学习入门系列 神经网络的构成.训练和算法 什么是神经网络 人工神经网络(Artificial Neural Network,ANN),简称神经网络(Neural Network,NN),是一种 ...

  4. PyTorch深度学习入门与实战(案例视频精讲)

    作者:孙玉林,余本国 著 出版社:中国水利水电出版社 品牌:智博尚书 出版时间:2020-07-01 PyTorch深度学习入门与实战(案例视频精讲)

  5. PyTorch深度学习入门

    作者:曾芃壹 出版社:人民邮电出版社 品牌:iTuring 出版时间:2019-09-01 PyTorch深度学习入门

  6. 深度学习入门 基于Python的理论与实现

    作者:斋藤康毅 出版社:人民邮电出版社 品牌:iTuring 出版时间:2018-07-01 深度学习入门 基于Python的理论与实现

  7. 干货|《深度学习入门之Pytorch》资料下载

    深度学习如今已经成为了科技领域中炙手可热的技术,而很多机器学习框架也成为了研究者和业界开发者的新宠,从早期的学术框架Caffe.Theano到如今的Pytorch.TensorFlow,但是当时间线来 ...

  8. 福利丨一门面向所有人的人工智能公开课:MIT 6.S191,深度学习入门

    对初学者来说,有没有易于上手,使用流行神经网络框架进行教学的深度学习课程?近日,麻省理工学院(MIT)正式开源了在线介绍性课程「MIT 6.S191:深度学习入门」.该课程包括一系列有关神经网络及其在 ...

  9. 深度学习入门指北——从硬件到软件

    作者:隔壁王大喵 近日,Rachel Thomas在fast.ai上发布了一篇博文<What you need to do deep learning>,他希望通过这篇文章回答一些深度学习 ...

  10. LeCun亲授的深度学习入门课:从飞行器的发明到卷积神经网络

    Root 编译整理 量子位 出品 | 公众号 QbitAI 深度学习和人脑有什么关系?计算机是如何识别各种物体的?我们怎样构建人工大脑? 这是深度学习入门者绕不过的几个问题.很幸运,这里有位大牛很乐意 ...

最新文章

  1. Relay外部库使用
  2. requirejs模块化html,requirejs模块化-入门
  3. python编程爱心-520用Python画一颗特别的爱心送给她
  4. Android中ActivityManager学习笔记
  5. 笔记(用Python做些事情)--变量(数字、字符串)
  6. 微信平台的几种分享方式调研
  7. 你知道这个C#开发跨平台APP的样例介绍开源项目吗?
  8. 【Leetcode | 1】3. 无重复字符的最长子串
  9. JavaScript 图片上传预览效果
  10. php操作redis_PHP操作Redis的基本方法
  11. Element Dialog弹框回到顶部
  12. 6. 生信技能树——TCGA癌症数据1
  13. 用户体验与可用性测试_读书笔记
  14. [T-ARA][I'm so bad]
  15. 字符串和数字相加,结果为字符串
  16. 金圣叹“不亦快哉”三十三则
  17. 金山的 wifi共享android手机怎莫共享台式机3g无线网络,巧妙开启笔记本WiFi共享 手机上网有神助...
  18. 79g道闸雷达_79G雷达安装注意事项
  19. 机械臂——D-H参数标定
  20. angular获取当前路由信息

热门文章

  1. centos7安装图形界面
  2. AngularJS的 $resource服务 关于CRUD操作
  3. Fiddler抓包7-post请求(json)
  4. 【emWin】例程二十二:窗口对象——Framewin
  5. ios在没有越狱的机器上安装插件
  6. jetty-maven-plugin
  7. (剑指Offer)面试题58:二叉树的下一个结点
  8. c++设计一个无法被继承的类
  9. 数学分析原理 定理 6.5
  10. 把苦难装在心里--《赢在中国》(2008-05-27)