本节用一个示例,说明开发基于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.6.1/protoc-3.6.1-linux-x86_64.zip -O ~/Downloads/protoc-3.6.1-linux-x86_64.zip
$ unzip ~/Downloads/protoc-3.6.1-linux-x86_64.zip -d ~/Downloads/
$ sudo cp Downloads/bin/protoc /usr/local/bin/
# 此处输入密码

分发的压缩文件中的include目录包含有Protocol Buffers原生的.proto文件。这些文件可以被引用,但是暂时还没遇见能用到的地方,后期如果有用到,另行说明。

验证安装

$ protoc --version
libprotoc 3.6.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