1. protobuf简介
Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。
更多资料可查看:https://developers.google.com/protocol-buffers/
需要了解两点
- protobuf是类似与json一样的数据描述语言(数据格式)
- protobuf非常适合于RPC数据交换格式
接着我们来看一下protobuf的优势和劣势:
优势:
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速度
劣势:
1:应用不够广(相比xml和json)
2:二进制格式导致可读性差
3:缺乏自描述
2. protobuf的安装
2.1 windows平台(vs2017)
- 安装protobuf
- 下载protobuf的C++版本的源代码,地址:https://github.com/google/protobuf/releases
- 下载 protobuf-cpp-3.8.0.zip/tar.gz 这个包
- 解压源码 (路径不要带中文)
- 安装cmake,下载地址:https://cmake.org/download/
- 使用cmake 生成 vs2017 工程
也可使用以下链接下载:
链接:https://pan.baidu.com/s/1NiOIb7hjhGoXj1_fkGQfyQ
提取码:x6xm
执行完这一步之后,需要稍等一小会儿。
- 进入 vs2017 工程目录,使用vs2017打开,
F7
编译 - 编译完成 ,在 vs2017工程目录 /Debug 目录下,可以看到生成的库文件
- 将生成的动态库和头文件放到自定义目录中备用
- 在vs中指定头文件目录和库目录(根据自己上一步实际的存储目录进行指定)
- 修改vs其他配置(这个很重要 )
- 修改预处理器定义:
- 项目属性->c/c++ -> 预处理器 -> 预处理器定义添加这个
PROTOBUF_USE_DLLS
宏定义
2.2Linux(C)下的安装
参考资料: https://github.com/protocolbuffers/protobuf/tree/master/src
- 下载源码安装包:
protobuf-cpp-3.8.0.tar.gz
- 解压缩
$ tar zxvf protobuf-cpp-3.8.0.tar.gz
- 安装 -> 进入到解压目录
$ cd protobuf-3.8.0
$ ./configure
$ make
$ make check (可选)
$ sudo make install
2.3Linux(go)下的安装
1.下载protobuf
方法一:===> git clone https://github.com/protocolbuffers/protobuf.git
方法二:===> 或者将准备好的压缩包进行拖入
链接:https://pan.baidu.com/s/1FcidcGTk-_pdmM-5PKjvwg 提取码:pk2f
解压到$GOPATH/src/github.com/protocolbuffers/下面
Unzip protobuf.zip
2.安装(ubuntu)
(1)安装依赖工具(联网)
$ sudo apt-get install autoconf automake libtool curl make g++ unzip libffi-dev -y
(2)进入protobuf文件
cd protobuf/
(3)进行安装检测 并生成自动安装脚本
./autogen.sh
./configure
(4)进行编译C代码
make
(5)进行安装
sudo make install
(6)刷新linux共享库关系
sudo ldconfig
3.测试protobuf编译工具
protoc -h
如果正常输出 相关指令 没有报任何error,为安装成功
4.安装protobuf的go语言插件
由于protobuf并没直接支持go语言需要我们手动安装相关插件
(1)下载
方法一:===> go get -v -u github.com/golang/protobuf/proto
方法二:===>或者将 github.com-golang-protobuf.zip拖入 进行解压到 $GOPATH/src/github.com/golang
(2)进入到文件夹内进行编译
$ cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go
$ go build
(3)将生成的 protoc-gen-go可执行文件,放在/bin目录下
$ sudo cp protoc-gen-go /bin/
(4)尝试补齐protoc-gen-go 如果可以补齐代表成功,如果执行不报错 代表工具成功
3. protobuf简单语法
参考文档:https://developers.google.com/protocol-buffers/docs/proto3 简单示例:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
}
enum test{
int32 age = 0;
}
- protobuf消息的定义(或者称为描述)通常都写在一个以 .proto 结尾的文件中。
- 该文件的第一行指定正在使用
proto3
语法:如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。- 第二行package指明当前是pb包(生成go文件之后和Go的包名保持一致)
- 最后message关键字定义一个Person消息体,类似于go语言中的结构体,是包含一系列类型数据的集合。许多标准的简单数据类型都可以作为字段类型,包括
bool
,int32
,float
,double
,和string
。也可以使用其他message类型作为字段类型。- 在message中有一个字符串类型的value成员,该成员编码时用1代替名字。我们知道,在json中是通过成员的名字来绑定对应的数据,但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。
message的格式说明:
消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式:
数据类型 字段名称 = 唯一的编号标签值;
唯一的编号标签:代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消息字段。编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。
4. protobuf高级语法
4.1message嵌套
messsage除了能放简单数据类型外,还能存放另外的message类型,如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
PhoneNumber phone = 3;
}
4.2 repeated关键字
repeadted关键字类似与go中的切片,编译之后对应的也是go的切片,用法如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
repeated PhoneNumber phone = 3;
}
4.3 默认值
解析数据时,如果编码的消息不包含特定的单数元素,则解析对象对象中的相应字段将设置为该字段的默认值。不同类型的默认值不同,具体如下:
对于字符串,默认值为空字符串。
对于字节,默认值为空字节。
对于bools,默认值为false。
对于数字类型,默认值为零。
对于枚举,默认值是第一个定义的枚举值,该值必须为0。
repeated字段默认值是空列表
message字段的默认值为空对象
4.4 enum关键字
在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表。比如说,电话号码字段有个类型,这个类型可以是,home,work,mobile。我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作。
enum也可以为不同的枚举常量指定相同的值来定义别名。如果想要使用这个功能必须讲allow_alias
选项设置为true,负责编译器将报错。示例如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
//如果不设置将报错
option allow_alias = true;
MOBILE = 0;
HOME = 1;
WORK = 2;
Personal = 2;
}
如上,enum的第一个常量映射为0,每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:
- 必须有一个零值,以便我们可以使用0作为数字默认值。
- 零值必须是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值。
4.5 oneof关键字
如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
oneof data{
string school = 5;
int32 score = 6;
}
}
4.6 在一个proto文件中导入其他proto文件
- 一个proto文件
syntax = "proto3"; // 第一行
// 导入 Info.proto 文件到当前文件中
import "Info.proto";
enum Color
{
Red = 0;
Green = 6;
Blue = 9;
Pink = 7;
}
message Person
{
int32 id = 1;
repeated bytes name = 2; // 代表数组,可以存多个数据
string sex = 3;
int32 age = 4;
Color color = 5;
Info info = 6;
}
- 另一个proto文件
syntax = "proto3";
message Info
{
bytes address = 1; //家庭住址
int32 num = 2; // 门牌号
}
- 将 Person.proto 和 Info.proto 各自调用 protoc 命令,各自生成对应的 xxxpb.h 和 xxxpb.cc
- 将 xxxpb.h 和 xxxpb.cc 导入VS项目中。
- 初始化Person时,初始化 Info
Info *info = p.mutable_info();
info->set_address("地址值");
info->set_num("门牌号");
- 解析获取Person 是,获取 Info数据。
Info ii = p.info();
ii.address(); // 获取地址值
ii.num(); // 获取门牌号
4.7 定义RPC服务
如果需要将message与RPC一起使用,则可以在.proto
文件中定义RPC服务接口,protobuf编译器将根据你选择的语言生成RPC接口代码。示例如下:
//定义RPC服务
service HelloService {
rpc Hello (Person)returns (Person);
}
5. protobuf基本编译
5.1 Linux下的编译
protobuf编译是通过编译器protoc进行的,通过这个编译器,我们可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,生成命令如下:
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
- –proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用–proto_path,它们将会顺序的被访问并执行导入。
- –go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
–cpp_out= 即可用于C++语言中- 允许使用
protoc --go_out=./ *.proto
的方式一次性编译多个 .proto 文件- go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件
一般在使用的时候我们都是使用下面这种简单的命令:
protoc --go_out=./ *.proto
当我们给这个.proto
文件中添加一个RPC服务,进行编译时略有不同需指定编译插件。在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码:
gRPC的安装及使用可看此博客: }
protoc --go_out=plugins=grpc:. *.proto
5.2 windows下的编译
5.2.1 操作流程
1.准备待序列化的数据。
2.创建 “xxxx.proto” 文件,指定使用protobuf版本(2、3)
3.按 protobuf 语法,将数据组织到文件中。
4.使用 命令 protoc
生成 xxxpb.h 和 xxxpb.cc
- 将 bin/ 配置到环境变量 PATH 中。C:\protobuf\bin
- 将 lib/ 配置到环境变量 PATH 中。C:\protobuf\lib
5.将 .h 和 .cc 添加到 VS 项目中。实现 序列化、反序列化。
- 读API
- xxx();
- 写API
- set_xxx();
- 序列化 : SerializePartialToString()
- 反序列化:ParseFromString()
5.2.2 测试使用protobuf编解码
- 准备工作:
- 将 xxx.pb.cc 和 xxx.pb.h 添加到项目中。
- 项目右键 – 属性 – VC++目录 – 包含目录 – 写入 C:\protobuf\include
- 项目右键 – 属性 – VC++目录 – 库目录 – 写入 C:\protobuf\lib
- 项目右键 – 属性 – 链接器 – 输入 – 附加依赖项 – 写入 libprotobufd.lib
- 项目右键 – 属性 – C/C++ – 预处理器 – 预处理器定义 – 写入 PROTOBUF_USE_DLLS 宏。
- 测试使用流程:
1.创建Person类对象,并初始化。
2.将 person类进行序列化 SerializePartialToString(),得到 string。
3.网络传输。
4.将得到的string,进行反序列化 ParseFromString(),得到Person 对象。
5.打印查看 类成员。