介绍
Google Protocol Buffer(简称Protobuf)是一种轻便、高效的结构化数据存储格式,平台无关、语言无关、可扩展、可用于通讯协议和数据存储等领域。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
json\xml都是基于文本格式,protobuf是二进制格式。
优势
序列化后体积比json和xml更小,适合网络传输
支持跨平台多语言
消息格式升级和兼容还不错
序列化和反序列化很快,快于json的处理速度
不足
功能简单,无法用来表示复杂概念。
通用性上不足。
不适合用来描述数据结构。
它以二进制方式存储,除非有.proto定义,否则没法直接读出protobuf的任何数据。
比较
项
说明
json
一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常友好,有很多内建的函数支持。
xml
在webservice中应用最为广泛,但是相比于json,它的数据更加冗余,因为需要成对的闭合标签。
protobuf
后起之秀,是google开源的一种数据格式,适合高性能、对响应速度有要求的数据传输场景。因为protobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。
安装
MacOS
CentOS
Ubuntu
12
> brew install protobuf
> protoc --version
12345678910111213141516171819
> yum -y install autoconf automake libtool make g++
> cd /usr/local/src/
> wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protobuf-all-3.12.0.tar.gz
> tar zxvf protobuf-all-3.12.0.tar.gz
> cd protobuf-3.12.0/
> ./autogen.sh
> ./configure --prefix=/usr/local/protobuf
> make
> make check
> make install
> ldconfig
> vim /etc/profile
##### 增加如下内容 #####
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib
export PATH=$PATH:/usr/local/protobuf/bin
##### 内容结束 #####
> source /etc/profile
> protoc --version
12345678910111213141516171819
> sudo apt-get install autoconf automake libtool curl make g++ libffi-dev -y
> cd /usr/local/src/
> wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protobuf-all-3.12.0.tar.gz
> tar zxvf protobuf-all-3.12.0.tar.gz
> cd protobuf-3.12.0/
> ./autogen.sh
> ./configure --prefix=/usr/local/protobuf
> make
> make check
> make install
> ldconfig
> vim /etc/profile
##### 增加如下内容 #####
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib
export PATH=$PATH:/usr/local/protobuf/bin
##### 内容结束 #####
> source /etc/profile
> protoc --version
使用
语法
syntax = "proto3";
message 消息名 {
消息体
}
示例
response.proto
// 指定protobuf的版本,proto3是最新的语法版本
syntax = "proto3";
// 定义数据结构,message 你可以想象成java的class,c语言中的struct
message Response {
string data = 1; // 定义一个string类型的字段,字段名字为data, 序号为1
int32 status = 2; // 定义一个int32类型的字段,字段名字为status, 序号为2
}
proto文件中,字段后面的序号,不能重复,定义了就不能修改,可以理解成字段的唯一ID。
分配标识号
在消息定义中,每个字段后面都有一个唯一的数字,这个就是标识号。
这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变,每个消息内唯一即可,不同的消息定义可以拥有相同的标识号。
[1,15]之内的标识号在编码的时候会占用一个字节。
[16,2047]之内的标识号则占用2个字节。
所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。
切记:要为将来有可能添加的、频繁出现的字段预留一些标识号。
保留标识号
message Foo {
reserved 2, 15, 9 to 11; // 保留2,15,9到11这些标识号
}
注释
往.proto文件添加注释,支持C/C++/java风格的双斜杠(//) 语法格式。
为消息定义包
package foo.bar;
message Open { ... }
选项
java_package 单独为java定义包名字。
java_outer_classname 单独为java定义,protobuf编译器生成的类名。
将消息编译成各种语言版本的类库
1234
##### 命令格式:
> protoc [OPTION] PROTO_FILES
##### 例子:
> protoc --java_out=. demo.proto
常用的OPTION选项:
--cpp_out=OUT_DIR 指定代码生成目录,生成 C++ 代码
--csharp_out=OUT_DIR 指定代码生成目录,生成 C# 代码
--java_out=OUT_DIR 指定代码生成目录,生成 java 代码
--js_out=OUT_DIR 指定代码生成目录,生成 javascript 代码
--objc_out=OUT_DIR 指定代码生成目录,生成 Objective C 代码
--php_out=OUT_DIR 指定代码生成目录,生成 php 代码
--python_out=OUT_DIR 指定代码生成目录,生成 python 代码
--ruby_out=OUT_DIR 指定代码生成目录,生成 ruby 代码
在代码中使用ProtoBuf对数据进行序列化和反序列化
maven:
12345
com.google.protobuf
protobuf-java
3.9.1
1234567891011121314151617
ResponseOuterClass.Response.Builder builder = ResponseOuterClass.Response.newBuilder();
// 设置字段值builder.setData("hello www.wanglibing.com");
builder.setStatus(200);
ResponseOuterClass.Response response = builder.build();
// 将数据根据protobuf格式,转化为字节数组 byte[] byteArray = response.toByteArray();
// 反序列化,二进制数据try {
ResponseOuterClass.Response newResponse = ResponseOuterClass.Response.parseFrom(byteArray);
System.out.println(newResponse.getData());
System.out.println(newResponse.getStatus());
} catch (Exception e) {
}
Golang使用protobuf
12345678910
##### 1. 安装protobuf编译器(步骤略)
##### 2. 安装protobuf包
> go get -v -u github.com/golang/protobuf/proto
##### 3. 安装protoc-gen-go插件
> go get -v -u github.com/golang/protobuf/protoc-gen-go
##### 4. 定义proto消息(步骤略)
##### 5. 不带插件编译
> protoc --go_out=./ *.proto
##### 5. 带插件编译
> protoc --go_out=plugins=grpc:./ *.proto
基本数据类型
double
float
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代
uint32 使用变长编码。
uint64 使用变长编码。
sint32 使用变长编码,这些编码在负值时比int32高效的多。
sint64 使用变长编码,有符号的整型值。编码时比通常的int64高效。
fixed32 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。
fixed64总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32 总是4个字节。
sfixed64 总是8个字节。
bool
string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes 可能包含任意顺序的字节数据。
枚举类型
syntax = "proto3";//指定版本信息,不指定会报错
enum PhoneType //枚举消息类型,使用enum关键词定义,一个电话类型的枚举类型
{
MOBILE = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
HOME = 1;
WORK = 2;
}
// 定义一个电话消息
message PhoneNumber
{
string number = 1; // 电话号码字段
PhoneType type = 2; // 电话类型字段,电话类型使用PhoneType枚举类型
}
数组类型
整数数组
message Msg {
// 只要使用repeated标记类型定义,就表示数组类型。
repeated int32 arrays = 1;
}
字符串数组
message Msg {
repeated string names = 1;
}
消息嵌套
引用其它消息类型
// 定义Result消息
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3; // 字符串数组类型
}
// 定义SearchResponse消息
message SearchResponse {
// 引用上面定义的Result消息类型,作为results字段的类型
repeated Result results = 1; // repeated关键词标记,说明results字段是一个数组
}
消息嵌套
message SearchResponse {
// 嵌套消息定义
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
// 引用嵌套的消息定义
repeated Result results = 1;
}
import导入其它proto文件定义的消息
result.proto
syntax = "proto3";
// Result消息定义
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3; // 字符串数组类型
}
search_response.proto
syntax = "proto3";
// 导入Result消息定义
import "result.proto";
// 定义SearchResponse消息
message SearchResponse {
// 使用导入的Result消息
repeated Result results = 1;
}
map类型
map语法
map map_field = N;
key_type可以是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型)。请注意,枚举不是有效的key_type。
value_type 可以是除另一个映射之外的任何类型。
map示例
syntax = "proto3";
message Product
{
string name = 1; // 商品名
// 定义一个k/v类型,key是string类型,value也是string类型
map attrs = 2; // 商品属性,键值对
}
Map 字段不能使用repeated关键字修饰。