1. protobuf简介

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。
更多资料可查看:https://developers.google.com/protocol-buffers/

需要了解两点

  1. protobuf是类似与json一样的数据描述语言(数据格式)
  2. 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

proto转换java protobufjs_windows


proto转换java protobufjs_proto转换java_02


执行完这一步之后,需要稍等一小会儿。

proto转换java protobufjs_go_03

  • 进入 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语言中的结构体,是包含一系列类型数据的集合。许多标准的简单数据类型都可以作为字段类型,包括boolint32floatdouble,和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
  1. –proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用–proto_path,它们将会顺序的被访问并执行导入。
  2. –go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
    –cpp_out= 即可用于C++语言中
  3. 允许使用 protoc --go_out=./ *.proto 的方式一次性编译多个 .proto 文件
  4. 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编解码
  • 准备工作:
  1. 将 xxx.pb.cc 和 xxx.pb.h 添加到项目中。
  2. 项目右键 – 属性 – VC++目录 – 包含目录 – 写入 C:\protobuf\include
  3. 项目右键 – 属性 – VC++目录 – 库目录 – 写入 C:\protobuf\lib
  4. 项目右键 – 属性 – 链接器 – 输入 – 附加依赖项 – 写入 libprotobufd.lib
  5. 项目右键 – 属性 – C/C++ – 预处理器 – 预处理器定义 – 写入 PROTOBUF_USE_DLLS 宏。
  • 测试使用流程:
    1.创建Person类对象,并初始化。
    2.将 person类进行序列化 SerializePartialToString(),得到 string。
    3.网络传输。
    4.将得到的string,进行反序列化 ParseFromString(),得到Person 对象。
    5.打印查看 类成员。