继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Protobuf学习 - 入门

慕田峪0738999
关注TA
已关注
手记 344
粉丝 88
获赞 494

古之立大事者,不惟有超世之才,亦必有坚忍不拔之志

                         -- 苏轼·《晁错论》

 

  从公司的项目源码中看到了这个东西,觉得挺好用的,写篇博客做下小总结。下面的操作以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. 编写proto文件 
  首先需要一个proto文件,其中定义了我们程序中需要处理的结构化数据: 

复制代码


复制代码

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三种代码文件,编译格式如下: 


上面的命令会生成xxx.pb.h 和 xxx.pb.cc两个C++文件。 

4. 使用C++文件 

  现在编写一个main.cc文件:

复制代码


复制代码

5. 常用API

  protoc为message的每个required字段和optional字段都定义了以下几个函数(不限于这几个):


为每个repeated字段定义了以下几个:


另外,下面几个是常用的序列化函数:


与之对应的反序列化函数:


其他常用的函数:


官方API文档地址: https://developers.google.com/protocol-buffers/docs/reference/overview

6. 编译生成可执行代码 

  编译格式和普通的C++代码一样,但是要加上 -lprotobuf -pthread 


7. 输出结果

复制代码


复制代码

 

三、怎么编码的? 

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

示例:

复制代码


复制代码

2. ZigZag编码

  Base-128变长编码会去掉整数前面那些没用的0,只保留低位的有效位,然而负数的补码表示有很多的1,所以protobuf先用ZigZag编码将所有的数值映射为无符号数,然后使用Base-128编码,ZigZag的编码规则如下:


负数右移后高位全变成1,再与左移一位后的值进行异或,就把高位那些无用的1全部变成0了,巧妙!

3. 消息格式

  每一个Protocol Buffers的Message包含一系列的字段(key/value),每个字段由字段头(key)和字段体(value)组成,字段头由一个变长32位整数表示,字段体由具体的数据结构和数据类型决定。 
字段头格式:


4. 字段编码类型

TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages(嵌套message), packed repeated fields
3Start groupgroups (废弃) 
4End groupgroups (废弃)
532-bitfixed32, sfixed32, float

 

   
 
 
 



 

 

 5. 编码示例(下面的编码以16进制表示)

复制代码


复制代码

 

四、还有什么? 

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++类型备注
doubledouble
floatfloat
int32int32使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint32
int64int64使用可变长编码,编码负数时不够高效——如果字段可能含有负数,请使用sint64
uint32uint32使用可变长编码
uint64uint64使用可变长编码
sint32int32使用可变长编码,有符号的整型值,编码时比通常的int32高效
sint64int64使用可变长编码,有符号的整型值,编码时比通常的int64高效
fixed32uint32总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效
fixed64uint64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效
sfixed32int32总是4个字节
sfixed64int64总是8个字节
boolbool
stringstring一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本
bytesstring可能包含任意顺序的字节数据


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

参考资料  
1. Protocol Buffers Developer Guide 

2. Google Protocol Buffer 的使用和原理

3. 浅谈几种序列化协议 

4. 序列化和反序列化 

5. Protobuf使用手册 

(本文完)

原文出处

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP