LarryDpk
发布于 2020-10-01 / 1820 阅读
0

Protobuf入门与使用示例,高性能的序列化框架

1 前言

首先还是感叹一下谷歌的可怕,做了这么多开创性的生产级别的开源产品,Protobuf就是其中一员。它是与开发语言无关、与平台无关的结构化数据的序列化框架。支持的语言有JavaC/C++PythonRubyJS等。使用它序列化后的数据比JsonXML小很多,所以在网络传输上有更好的性能表现。

但要注意,与JsonXML不同,人类无法直接或直观地阅读被Protobuf序列化后的结果。所以需要通过把接收到的数据反序列化后再通过各自语言处理展示。

2 安装与配置

根据自己的系统,到Github Release下载对应的程序,我这里下载的是protoc-3.13.0-osx-x86_64.zip。我下载后放在/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文件,先装一个ProtobufIDEA插件,以方便编写及高亮。

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是消息体的定义,stringint32这些都是数据类型,后面的数据表示序号,不可重复。

我们还定义了子消息体Server,嵌入到WebSite中去。

3.1.3 数据类型

数据类型如下:

.proto TypeC++ TypeJava TypePython Type[2]Go TypeRuby TypeC# TypePHP TypeDart Type
doubledoubledoublefloatfloat64Floatdoublefloatdouble
floatfloatfloatfloatfloat32Floatfloatfloatdouble
int32int32intintint32Fixnum or Bignum (as required)intintegerint
int64int64longint/longint64Bignumlonginteger/stringInt64
uint32uint32intint/longuint32Fixnum or Bignum (as required)uintintegerint
uint64uint64longint/longuint64Bignumulonginteger/stringInt64
sint32int32intintint32Fixnum or Bignum (as required)intintegerint
sint64int64longint/longint64Bignumlonginteger/stringInt64
fixed32uint32intint/longuint32Fixnum or Bignum (as required)uintintegerint
fixed64uint64longint/longuint64Bignumulonginteger/stringInt64
sfixed32int32intintint32Fixnum or Bignum (as required)intintegerint
sfixed64int64longint/longint64Bignumlonginteger/stringInt64
boolboolbooleanboolboolTrueClass/FalseClassboolbooleanbool
stringstringStringstr/unicodestringString (UTF-8)stringstringString
bytesstringByteStringstr[]byteString (ASCII-8BIT)ByteStringstringList

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


参考资料:

编写proto3官网Language Guide (proto3)