NEWS LETTER

Protobuf浅解

Scroll down

简介

Protocol Buffers(简称 Protobuf)是一种由 Google 开发的、用于数据交换的高效序列化协议。它具有语言无关、平台无关、可扩展等特点,可用于数据通信协议和数据存储。与 XML 类似,Protobuf 可以定义数据结构,并通过生成的源代码在各种语言中编写和读取结构化数据,但 Protobuf 的数据体积更小(310 倍)、速度更快(20100 倍),且更加简单。

Protobuf 提供了一种灵活、高效、自动化的机制来序列化结构化数据。定义数据结构后,可以使用自动生成的源代码轻松在各种数据流中使用各种语言进行编写和读取。即使更新数据结构,也不会破坏使用旧数据结构编译的已部署程序。

由于其高性能,大部分即时通讯协议(如支付宝、微信、QQ 等)都采用 Protobuf 进行数据传输。

安装

从项目 GitHub Releases 下载需要的压缩包

例如我是在 windows 进行开发,所以下载 protoc-3.14.0-win64.zip,其它操作系统见官方文档

解压后在 protoc-3.14.0-win64\bin 路径下有一个 protoc.exe 程序,为了方便我这边是放到了 C:\Windows\System32 目录下,也可以放任意目录

项目地址:https://github.com/protocolbuffers/protobuf/releases

Java ProtoBuf Demo

编写描述文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 指定proto3语法,否则默认为proto2
syntax = "proto3";

package demo;

// 指定包名
option java_package = "com.mikuac.demo";
// 指定类名
option java_outer_classname = "ProtoBufTest";

message SearchRequest {
// 指定字段类型为string,标识号为 1
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}

编译生成 Java 代码,Protobuf 编译器通过描述文件(.proto 文件)生成对应于语言的代码,代码中定义了消息类型、获取、设置、编解码序列化等操作。这也是为什么 Protobuf 支持跨语言传输,因为消息所有端共用一个通用的描述文件。

1
2
3
protoc --java_out=. test.proto
#在当前目录生成ProtoBufTest.java

1
2
3
4
5
6
7
8
# 引入Maven依赖
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.0.0-rc-2</version>
</dependency>

SpringBoot Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);

System.out.println("----------开始构建模型----------");
ProtoBufTest.SearchRequest.Builder searchRequestBuilder = ProtoBufTest.SearchRequest.newBuilder();
searchRequestBuilder.setQuery("测试");
searchRequestBuilder.setPageNumber(10);
searchRequestBuilder.setResultPerPage(1);

ProtoBufTest.SearchRequest searchRequestData = searchRequestBuilder.build();
System.out.println(searchRequestData.toString());
System.out.println("----------模型构建结束----------");

System.out.println("\n----------开始序列化----------");
for (byte b : searchRequestData.toByteArray()) {
System.out.print(b);
}
System.out.println("\n----------序列化完成----------");
System.out.println("\n" + "bytes长度" + searchRequestData.toByteString().size());

System.out.println("\n----------反序列化开始----------");
ProtoBufTest.SearchRequest sr = null;
try {
sr = ProtoBufTest.SearchRequest.parseFrom(searchRequestData.toByteArray());
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println(sr);
System.out.println("----------反序列化完成----------");
}

}

运行结果

ProtoBuf2 的三个关键字

  • required:表示该字段必须有值
  • optional:消息格式中该字段可以有 0 个或 1 个值(不超过 1 个)
  • repeated:该字段可以重复任意次(包括 0 次),重复的值的顺序会被保留,表示该值可以重复,相当于 java 中的 List

ProtoBuf3 移除 required 与 optional


proto3 仅支持 repeated 字段修饰,如果使用 required,optional 编译会报错

分配标识号

每个字段都有唯一的数字标识号,需要注意的是 [1,15] 之内的标识在编码时只占用 1 字节,[16,2047] 之内的占用 2 字节,所以应该对那些频繁出现的消息元素预留 [1,15] 之内的标识号

标量数值类型

一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto 文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type
double double double float float64 Float double float
float float float float float32 Float float float
int32 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用 sint64 替代 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
uint32 使用变长编码 uint32 int int/long uint32 Fixnum 或者 Bignum(根据需要) uint integer
uint64 使用变长编码 uint64 long int/long uint64 Bignum ulong integer/string
sint32 使用变长编码,这些编码在负值时比 int32 高效的多 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sint64 使用变长编码,有符号的整型值。编码时比通常的 int64 高效。 int64 long int/long int64 Bignum long integer/string
fixed32 总是 4 个字节,如果数值总是比总是比 228 大的话,这个类型会比 uint32 高效。 uint32 int int uint32 Fixnum 或者 Bignum(根据需要) uint integer
fixed64 总是 8 个字节,如果数值总是比总是比 256 大的话,这个类型会比 uint64 高效。 uint64 long int/long uint64 Bignum ulong integer/string
sfixed32 总是 4 个字节 int32 int int int32 Fixnum 或者 Bignum(根据需要) int integer
sfixed64 总是 8 个字节 int64 long int/long int64 Bignum long integer/string
bool bool boolean bool bool TrueClass/FalseClass bool boolean
string 一个字符串必须是 UTF-8 编码或者 7-bit ASCII 编码的文本。 string String str/unicode string String (UTF-8) string string
bytes 可能包含任意顺序的字节数据。 string ByteString str []byte String (ASCII-8BIT) ByteString string

默认值

当一个消息被解析的时候,如果被编码的信息不包含一个特定的 singular 元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:

  • 对于 strings,默认是一个空 string
  • 对于 bytes,默认是一个空的 bytes
  • 对于 bools,默认是 false
  • 对于数值类型,默认是 0
  • 对于枚举,默认是第一个定义的枚举值,必须为 0;
  • 对于消息类型(message),域没有被设置,确切的消息是根据语言确定的,详见generated code guide
  • 对于可重复域的默认值是空(通常情况下是对应语言中空列表)

从.proto 描述文件生成了什么?

当用 protocol buffer 编译器来运行.proto 文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto 文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。

  • 对 C++来说,编译器会为每个.proto 文件生成一个.h 文件和一个.cc 文件,.proto 文件中的每一个消息有一个对应的类。
  • 对 Java 来说,编译器为每一个消息类型生成了一个.java 文件,以及一个特殊的 Builder 类(该类是用来创建消息类接口的)。
  • 对 Python 来说,有点不太一样——Python 编译器为.proto 文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的 Python 数据访问类。
  • 对 go 来说,编译器会位每个消息类型生成了一个.pd.go 文件。
  • 对于 Ruby 来说,编译器会为每个消息类型生成了一个.rb 文件。
  • javaNano 来说,编译器输出类似域 java 但是没有 Builder 类
  • 对于 Objective-C 来说,编译器会为每个消息类型生成了一个 pbobjc.h 文件和 pbobjcm 文件,.proto 文件中的每一个消息有一个对应的类。
  • 对于 C#来说,编译器会为每个消息类型生成了一个.cs 文件,.proto 文件中的每一个消息有一个对应的类。
其他文章
目录导航 置顶
  1. 1. 简介
  2. 2. 安装
  3. 3. Java ProtoBuf Demo
  4. 4. 运行结果
  5. 5. ProtoBuf2 的三个关键字
  6. 6. ProtoBuf3 移除 required 与 optional
  7. 7. 分配标识号
  8. 8. 标量数值类型
  9. 9. 默认值
  10. 10. 从.proto 描述文件生成了什么?
请输入关键词进行搜索