Protobuf

 ·  ☕ 6  · 👀...

介绍

Google Protocol Buffer(简称Protobuf)是一种轻便、高效的结构化数据存储格式,平台无关、语言无关、可扩展、可用于通讯协议数据存储等领域。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
json\xml都是基于文本格式,protobuf是二进制格式。

优势

  1. 序列化后体积比json和xml更小,适合网络传输
  2. 支持跨平台多语言
  3. 消息格式升级和兼容还不错
  4. 序列化和反序列化很快,快于json的处理速度

不足

  1. 功能简单,无法用来表示复杂概念。
  2. 通用性上不足。
  3. 不适合用来描述数据结构。
  4. 它以二进制方式存储,除非有.proto定义,否则没法直接读出protobuf的任何数据。

比较

说明
json 一般的web项目中,最流行的主要还是json。因为浏览器对于json数据支持非常友好,有很多内建的函数支持。
xml 在webservice中应用最为广泛,但是相比于json,它的数据更加冗余,因为需要成对的闭合标签。
protobuf 后起之秀,是google开源的一种数据格式,适合高性能、对响应速度有要求的数据传输场景。因为protobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

安装

1
2
> brew install protobuf
> protoc --version
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
> 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
> 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。

分配标识号

在消息定义中,每个字段后面都有一个唯一的数字,这个就是标识号。
这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变,每个消息内唯一即可,不同的消息定义可以拥有相同的标识号。

保留标识号

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编译器生成的类名。

将消息编译成各种语言版本的类库

1
2
3
4
##### 命令格式:
> 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:

1
2
3
4
5
<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>3.9.1</version>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
##### 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<key_type, value_type> map_field = N;
key_type可以是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型)。请注意,枚举不是有效的key_type。
value_type 可以是除另一个映射之外的任何类型。

map示例

syntax = "proto3";
message Product
{
    string name = 1; // 商品名
    // 定义一个k/v类型,key是string类型,value也是string类型
    map<string, string> attrs = 2; // 商品属性,键值对
}
Map 字段不能使用repeated关键字修饰。

Wanglibing
Wanglibing
Engineer,Lifelong learner