# Protobuf入门与使用示例,高性能的序列化框架
# 1 前言
首先还是感叹一下谷歌的可怕,做了这么多开创性的生产级别的开源产品,Protobuf
就是其中一员。它是与开发语言无关、与平台无关的结构化数据的序列化框架。支持的语言有Java
、C/C++
、Python
、Ruby
、JS
等。使用它序列化后的数据比Json
和XML
小很多,所以在网络传输上有更好的性能表现。
但要注意,与Json
和XML
不同,人类无法直接或直观地阅读被Protobuf
序列化后的结果。所以需要通过把接收到的数据反序列化后再通过各自语言处理展示。
# 2 安装与配置
根据自己的系统,到Github Release (opens new window)下载对应的程序,我这里下载的是protoc-3.13.0-osx-x86_64.zip (opens new window)。我下载后放在/Users/larry/Software/protobuf
目录,然后解压:
$ unzip protoc-3.13.0-osx-x86_64.zip
解压后可执行文件就在bin
目录下。
配置环境变量如下:
export PROTOBUF_HOME=/Users/larry/Software/protobuf
export PATH=$PATH:$PROTOBUF_HOME/bin
检查是否已经成功安装和配置:
$ protoc --version
libprotoc 3.13.0
正确显示版本号,安装成功。
# 3 proto文件使用
在Protobuf
中,用proto文件
来定义结构化数据,即Message
。它是非常易于编写的,也简单直观。接下来我们写一个简单的例子。
# 3.1 编写proto文件
# 3.1.1 安装插件
我们将编写一个WebSite.proto
文件,先装一个Protobuf
的IDEA
插件,以方便编写及高亮。
# 3.1.2 编写消息体
我们编写的proto
文件内容如下:
syntax = "proto3";
option java_package = "com.pkslow.proto";
option java_generate_equals_and_hash = true;
option java_outer_classname = "PkslowWebSite";
message WebSite {
string name = 1;
string link = 2;
int32 age = 3;
message Server {
string hostname = 1;
int32 port = 2;
}
Server server = 4;
}
看上是很清晰的:
syntax
指定为proto3
;
option
可以指定一些配置;
message
是消息体的定义,string
和int32
这些都是数据类型,后面的数据表示序号,不可重复。
我们还定义了子消息体Server
,嵌入到WebSite
中去。
# 3.1.3 数据类型
数据类型如下:
.proto Type | C++ Type | Java Type | Python Type[2] | Go Type | Ruby Type | C# Type | PHP Type | Dart Type |
---|---|---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double | float | double |
float | float | float | float | float32 | Float | float | float | double |
int32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
int64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
uint32 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
uint64 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 |
sint32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sint64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
fixed32 | uint32 | int | int/long | uint32 | Fixnum or Bignum (as required) | uint | integer | int |
fixed64 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 |
sfixed32 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | integer | int |
sfixed64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 |
bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool |
string | string | String | str/unicode | string | String (UTF-8) | string | string | String |
bytes | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string | List |
# 3.1.4 默认值
对于字符串,默认值为空串;
对于字节,默认值为空字节;
对于布尔型,默认值为false
;
对于数字类型,默认值为0
;
对于枚举值,默认值为第一个定义的枚举值,其实就是0
;
对于嵌入式的消息体,不同语言有不同表现。
# 3.1.5 编译
编写完proto
文件之后,就可以编译出不同语言对应的类了,我们编译Java
的类如下:
$ protoc --java_out=./ WebSite.proto
这样在当前目录就会生成Java
类如下:
# 3.2 Java使用输出类
前面我们已经编写了proto
文件,并生成了对应的Java
类,现在我们来使用它。
我们重新生成Java
类,直接放在Java
的目录下:
$ protoc --java_out=../java WebSite.proto
引入依赖如下:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.9.2</version>
</dependency>
Java
代码及关键注释如下:
package com.pkslow.proto;
import com.google.protobuf.InvalidProtocolBufferException;
public class ProtobufMain {
public static void main(String[] args) {
System.out.println("------create message and serialize------");
//创建嵌入式消息类Server
PkslowWebSite.WebSite.Server.Builder serverBuilder = PkslowWebSite.WebSite.Server.newBuilder();
PkslowWebSite.WebSite.Server server = serverBuilder.setHostname("1024.511.10.1")
.setPort(80)
.build();
//创建主消息类WebSite
PkslowWebSite.WebSite.Builder webSiteBuilder = PkslowWebSite.WebSite.newBuilder();
PkslowWebSite.WebSite webSite = webSiteBuilder
.setName("pkslow")
.setLink("www.pkslow.com")
.setAge(1)
.setServer(server)
.build();
//打印结果
System.out.println("webSite: " + webSite);
//序列化,可用于网络传输等
byte[] data = webSite.toByteArray();
System.out.println("------deserialize------");
//反序列化
try {
PkslowWebSite.WebSite deserializedWebSite = PkslowWebSite.WebSite.parseFrom(data);
//打印结果
System.out.println("deserializedWebSite: " + deserializedWebSite);
System.out.println("------compare------");
//结果为false,说明是另一个类
System.out.println(webSite == deserializedWebSite);
//结果为true,说明序列化和反序列化正确
System.out.println(webSite.equals(deserializedWebSite));
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
}
程序执行后的输出日志如下:
------create message and serialize------
webSite: name: "pkslow"
link: "www.pkslow.com"
age: 1
server {
hostname: "1024.511.10.1"
port: 80
}
------deserialize------
deserializedWebSite: name: "pkslow"
link: "www.pkslow.com"
age: 1
server {
hostname: "1024.511.10.1"
port: 80
}
------compare------
false
true
# 4 总结
本文旨在通过一个从编写到Java
使用的过程,让大家有一个大概的了解和感性认识。Protobuf
其实本来也不是什么复杂的东西,但它的应用却是很广的,比如gRPC
。
项目的代码在:https://github.com/LarryDpk/pkslow-samples
参考资料: