protobuf的使用

2018/09/12

介绍

Protobuf是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。

优点

  • 平台无关,语言无关,可扩展;
  • 提供了友好的动态库,使用简单;
  • 解析速度快,比对应的XML快约20-100倍;
  • 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

编译

安装 vs

https://blog.csdn.net/yuan_lo/article/details/67632128

安装 cmake

https://cmake.org/download/

编译安装

源码下载地址 https://github.com/google/protobuf

Windows还要下载protobuf-2.5.0-windows.zip。解压并把protoc.exe放在Protobuf安装目录下的src里。

yum install -y autoconf automake libtool curl make g++ unzip 安装依赖的库
./autogen.sh
./configure
make
make check
make install

实例

编写 addressbook.proto

syntax="proto2"; // 表明使用protobuf的编译器版本为v2,目前最新的版本为v3
package addressbook; // 声明了一个包名,用来防止不同的消息类型命名冲突,类似于 namespace
option java_package = "com.jado.ljh.protobuf";
option java_outer_classname = "HeartBeat";

// import "src/help.proto"; // 导入了一个外部proto文件中的定义,类似于C++中的 include 。不过好像只能import当前目录及当前目录的子目录中的proto文件。

// required:该值是必须要设置的;
// optional :该字段可以有0个或1个值(不超过1个);
// repeated:该字段可以重复任意多次(包括0次),类似于C++中的list;

message Person {	//结构化数据,类似于C++中的类
  required string name = 1; // 声明了一个名为name,数据类型为string的required字段,字段的标识号为1
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {	// 类似于C++中的类内声明,Person外部的结构可以用 Person.PhoneType 的方式来使用PhoneType。
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
    repeated Person person_info = 1;
}

编译成java类

protoc.exe -I=D:/project/git/spring_boot/protobuf --java_out=d:/tmp D:/project/git\spring_boot/protobuf/src/main/resources/com/hry/spring/proto/simple/*.proto
# protoc.exe -I=proto的输入目录 --java_out=java类输出目录 proto的输入目录包括包括proto文件

测试

// 模拟将对象转成byte[],方便传输
PersonEntity.Person.Builder builder = PersonEntity.Person.newBuilder();
builder.setId(1);
builder.setName("ant");
builder.setEmail("ghb@soecode.com");
PersonEntity.Person person = builder.build();
System.out.println("before :"+ person.toString());

System.out.println("===========Person Byte==========");
for(byte b : person.toByteArray()){
  System.out.print(b);
}
System.out.println();
System.out.println(person.toByteString());
System.out.println("================================");

//模拟接收Byte[],反序列化成Person类
byte[] byteArray =person.toByteArray();
Person p2 = Person.parseFrom(byteArray);
System.out.println("after :" +p2.toString());

protobuf与maven集成

难题

  • 多平台protoc 团队中有人使用mac 有人使用windows开发,编译使用的protoc是二进制程序,根据不同操作系统有不同的版本。
  • 按需编译 有一些模块(module)包含有proto目录,需要编译其中的.proto文件。另一些模块不包含proto目录,不需要编译。
  • 跨模块引用 有一些模块需要引用到其他模块的proto目录。

全局属性

<protobuf.input.directory>${project.basedir}/src/main/proto</protobuf.input.directory>
<protobuf.output.directory>src/main/gen-java</protobuf.output.directory>
<protobuf.protoc.path>${settings.localRepository}/protoc</protobuf.protoc.path>
<protobuf.version>3.2.0</protobuf.version>

引用protobuf-API:编译成的.java文件需要依赖这些库文件

<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>${protobuf.version}</version>
</dependency>

识别系统类型:区分Windows/Linux/OS X 不同系统,区分32/64-bit,识别结果会写入到变量里面

<build>
  <extensions>
    <!-- provides os.detected.classifier (i.e. linux-x86_64, osx-x86_64) property -->
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.4.1.Final</version>
    </extension>
  </extensions>
</build>

自动下载protoc到本地目录

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <version>2.10</version>
  <executions>
    <execution>
      <id>copy-protoc</id>
      <phase>generate-sources</phase>
      <goals>
        <goal>copy</goal>
      </goals>
      <configuration>
        <artifactItems>
          <artifactItem>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protoc</artifactId>
            <version>${protobuf.version}</version>
            <classifier>${os.detected.classifier}</classifier>
            <type>exe</type>
            <overWrite>false</overWrite>
            <outputDirectory>${protobuf.protoc.path}</outputDirectory>
          </artifactItem>
        </artifactItems>
      </configuration>
    </execution>
  </executions>
</plugin>

编译.proto文件

<propertyregex property="proto.include"
      input="${protobuf.input.directory}"
      regexp=";"
      replace=" -I"
      defaultvalue="${protobuf.input.directory}"
      global="true" />

最终呈现的插件配置

<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <dependencies>
    <dependency>
      <groupId>ant-contrib</groupId>
      <artifactId>ant-contrib</artifactId>
      <version>1.0b3</version>
      <exclusions>
        <exclusion>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.ant</groupId>
      <artifactId>ant-apache-regexp</artifactId>
      <version>1.8.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.ant</groupId>
      <artifactId>ant-nodeps</artifactId>
      <version>1.8.1</version>
    </dependency>
  </dependencies>
  <executions>
    <execution>
      <id>compile-protoc</id>
      <phase>generate-sources</phase>
      <configuration>
        <tasks>
          <taskdef resource="net/sf/antcontrib/antcontrib.properties"/>
          <available property="isExist" file="src/main/proto" type="dir"/>
          <if>
            <equals arg1="${isExist}" arg2="true"/>
            <then>
              <property name="protoc.filename"
                      value="protoc-${protobuf.version}-${os.detected.classifier}.exe"/>
              <property name="protoc.filepath"
                      value="${protobuf.protoc.path}/${protoc.filename}"/>
              <propertyregex property="proto.include"
                         input="${protobuf.input.directory}"
                         regexp=";"
                         replace=" -I"
                         defaultvalue="${protobuf.input.directory}"
                         global="true" />
              <chmod file="${protoc.filepath}" perm="ugo+rx"/>

              <path id="proto.path">
                <fileset dir="src/main/proto">
                  <include name="**/*.proto"/>
                </fileset>
              </path>

              <mkdir dir="src/main/gen-java"/>
              <pathconvert pathsep=" " property="proto.files" refid="proto.path"/>
              <exec executable="${protoc.filepath}" failonerror="true">
                <arg value="--java_out=${protobuf.output.directory}"/>
                <arg value="-I${proto.include}"/>
                <arg line="${proto.files}"/>
              </exec>
            </then>
          </if>
        </tasks>
        <sourceRoot>${protobuf.output.directory}</sourceRoot>
      </configuration>
      <goals>
        <goal>run</goal>
      </goals>
    </execution>
  </executions>
</plugin>

注意

  1. 使用maven-antrun的if语法,可以判断目录是否存在。如果目录存在,才开始编译工作。
  2. 在命令行调用protoc文件的时候,可以加入多个-I参数引用多个路径。
  3. 这里我们利用maven-antrun的propertyregex 工具,进行字符串替换。需要跨模块引用时,只需覆写protobuf.input.directory属性,多个目录之间用分号间隔。
  4. 插件依赖: if语法需要依赖插件ant-contrib:ant-contrib
  5. 插件依赖: propertyregex工具需要引用 org.apache.ant:ant-apache-regexp
  6. propertyregex 在匹配失败(regexp一个都没有匹配到)的时候,不会设置property;因此需要加入defaultvalue 设置成源串。

参考网址

https://blog.csdn.net/EatAnApple/article/details/78891717

目录