02.Protocol Buffers的一个示例
本节用一个示例,说明开发基于Protocol Buffers
的数据序列化与反序列化的处理过程。
说明
示例在Windows环境下使用Protocol Buffers
的Java API序列化一个Person,并存储到文件中,将文件复制到Linux环境,使用golang的API反序列化并输出到标准输出。
前置操作
安装Protocol Compiler
下载地址
https://github.com/protocolbuffers/protobuf/releases
windows安装
下载最新的Protocol Compiler,解压后的目录如下
|-- bin
| `-- protoc.exe
|-- include
| `-- google
| `-- protobuf
| |-- any.proto
| |-- api.proto
| |-- compiler
| | `-- plugin.proto
| |-- descriptor.proto
| |-- duration.proto
| |-- empty.proto
| |-- field_mask.proto
| |-- source_context.proto
| |-- struct.proto
| |-- timestamp.proto
| |-- type.proto
| `-- wrappers.proto
`-- readme.txt
将bin目录下的protoc.exe
复制到系统的某个PATH目录,或者将bin目录添加到系统PATH中。
Linux安装
$ mkdir -p ~/Downloads/
# 替换这里的链接为最新版本链接,文件名同样替换
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-linux-x86_64.zip -O ~/Downloads/protoc-3.6.1-linux-x86_64.zip
$ mkdir -p /tmp/protoc
$ unzip ~/Downloads/protoc-3.19.1-linux-x86_64.zip -d /tmp/protoc
$ sudo cp /tmp/protoc/bin/protoc /usr/local/bin/
$ sudo cp -r /tmp/protoc/include/google /usr/local/include
$ sudo chmod -R 755 /usr/local/include/google
验证安装
$ protoc --version
libprotoc 3.19.1
安装Java与Maven
略
安装Golang并配置GOPATH
略
序列化
创建Maven项目
目录结构如下
.
|-- pom.xml
`-- src
|-- main
| |-- java
| | `-- org
| | `-- cloud
| | `-- app
| | `-- main
| | `-- Main.java
| `-- resources
| |-- config.properties
| |-- logback.xml
| `-- proto
| `-- Person.proto
`-- test
|-- java
`-- resources
`-- TestConfig.properties
文件内容如下:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.cloud.app</groupId>
<artifactId>testing</artifactId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<name>standard</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<compiler.version>1.8</compiler.version>
<logback.version>1.2.3</logback.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.name}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${compiler.version}</source>
<target>${compiler.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%class{50} %thread] %-5level %logger{36} | %msg%n" />
<property name="LOG_FOLDER" value="logs" />
<property name="LOG_FILE_NAME" value="logs" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FOLDER}/${LOG_FILE_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FOLDER}/%d{yyyy-MM,aux}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>500MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FOLDER}/Error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FOLDER}/%d{yyyy-MM,aux}/Error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>500MB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<root level="trace">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>
编写proto文件
src/main/resources/proto/Person.proto
syntax = "proto3";
package entity;
option go_package = "entity/person";
option java_package = "org.cloud.app.entity";
option java_outer_classname = "Person";
message PersonType {
string name = 1;
int32 id = 2;
string email = 3;
}
生成Java类
在命令行中进入项目目录执行以下命令
$ protoc --java_out=src/main/java/ src/main/resources/proto/Person.proto
此命令会在src/main/java/
下生成相应的包和java文件。
实现main方法
src/main/java/org/cloud/app/main/Main.java
package org.cloud.app.main;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.cloud.app.entity.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
Person.PersonType.Builder personBuild = Person.PersonType.newBuilder();
personBuild.setId(1);
personBuild.setName("cloud");
personBuild.setEmail("cloudfstrife@sina.cn");
Person.PersonType person = personBuild.build();
BufferedOutputStream output = null ;
try {
output = new BufferedOutputStream(new FileOutputStream(new File("E:\\person.out")));
output.write(person.toByteArray());
} catch (IOException e) {
log.error("写入文件异常", e);
}finally {
if(output!=null) {
try {
output.flush();
output.close();
} catch (IOException e) {
log.error("流关闭异常", e);
}
}
}
}
}
测试
在命令行中进入项目目录执行以下命令
$ mvn clean package
$ mvn exec:java -Dexec.mainClass="org.cloud.app.main.Main"
如果执行成功,此时E:\目录下会生成person.out文件。里面包含了person序列化之后的数据。
反序列化
创建golang项目
在linux环境的shell中执行以下命令创建项目
go mod init app/deserializer
创建以下目录结构
.
├── go.mod
├── main.go
└── proto
└── Person.proto
proto/Person.proto
与序列化使用的文件相同
安装protoc的go模块插件
下载地址: https://github.com/protocolbuffers/protobuf-go/releases
下载完成之后,解压,放到 PATH
环境变量目录下即可。
生成Go文件
$ protoc --go_out=. proto/*.proto
生成文件如下
.
├── entity
│ └── person
│ └── Person.pb.go
├── go.mod
├── go.sum
├── main.go
└── proto
└── Person.proto
实现main方法
main.go
package main
import (
"app/serializer/entity/person"
"fmt"
"io/ioutil"
"log"
"os"
"github.com/golang/protobuf/proto"
)
func main() {
file, err := os.Open("./person.out")
if err != nil {
log.Fatalf("File Open Error:%v", err)
}
personByte, err := ioutil.ReadAll(file)
if err != nil {
log.Fatalf("File Read Error:%v", err)
}
personEntity := &person.PersonType{}
proto.Unmarshal(personByte, personEntity)
fmt.Println(personEntity)
}
编译运行
编译
$ go build
上传windows环境生成的person.out
到当前目录并执行以下内容
$ ./deserializer
name:"cloud" id:1 email:"cloudfstrife@sina.cn"
本来想用go module的,但是
github.com/golang/protobuf
的最新tag是1.2.0
(写文章时),生成的代码中使用的是最新版本的protobuf库,所以,build 的时候会报下面的错误
entity/person/Person.pb.go:21:11: undefined: proto.ProtoPackageIsVersion3
在这里用了
GOPATH
的方式。以后如果go module技术成熟了,再改成go module的实现
更新: 已使用 go module 模式 – 2020-09-24
总结
以上就是一个最简单的Protocol Buffers
序列化与反序列化的示例。开发过程主要分为以下几步:
定义.proto文件
使用protoc生成实体类
创建对象
执行序列化或者反序列化操作
过程还是非常简单的。就这样,祝好。
参考链接
https://github.com/protocolbuffers/protobuf
https://developers.google.com/protocol-buffers/docs/gotutorial
https://developers.google.com/protocol-buffers/docs/javatutorial