Protocol Buffers - 我在项目中的实践

目录

 

前言

正文

准备.proto文件

生成Java文件

使用生成的Java文件进行Http数据传输

Http方式的数据传输

收尾


前言

       在现有的项目开发中,应用之间大多都再使用Json方式的序列化数据传输,因为其小巧,快速,可读性好等原因,每个开发或多或少都接触过,或在项目开发中作为首选方式。

       然而,作为追求极致的从业者,在高并发,对性能要求很高的项目中,哪怕在序列化方面有一点提升,对整体的性能也是有很可观的效果,Protocol Buffer就是Google的开发团队不断追求效率的同时又兼顾性能,空间比的产物,相对Json,在项目有严苛要求序列化操作时,可以优先考虑使用此种方式。

       当然,现在很多朋友都有知道Protocol Buffer,但具体怎么使用,开发步骤都不太了解,本文结合项目中的实践,阐述如何使用,并且怎样通过 Java 版本的 Http方式传输接收Protobuf格式的数据。

正文

准备.proto文件

       .proto文件类似.json,.xml文件,是Protobuf协议格式的一种文件表示形式,Google提供了相应的工具,可以生成大多主流语言的代码,本文采用Java代码方式,编写相应的代码,就可以按照Protobuf协议格式所序列化的二进制进行应用间的传输。

       在项目开发中,如果是自己提供服务,这种格式的文件需要开发根据业务需求编写,如果是调用其他人的服务,一般对方都会提供相应的.proto文件。

       假设,项目中已经获取到了person.proto文件,具体的内容如下:

// protobuf 协议
syntax = "proto2";

// 指定转换后java类的包名
option java_package = "com.steven.entity";
// 指定转换后java类名
option java_outer_classname = "PersonProtobuf";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

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

  repeated PhoneNumber phone = 4;
}

 具体如何编写以上文件,可以参照Google官方文档谷歌官方编写proto文件的阐述

生成Java文件

       这个工具不得不说,Google开发团队也太强大了,作为一个与平台与语言无关的协议,真的需要做到让使用者觉察不到,是需要很大的决心和能力的,但也不得不吐槽下,如果有类似fastjson,gson等,可以跳过.proto文件的编写,Java中直接通过jar包形式,将Java类直接转换成Protobuf协议的二进制流传输就好了!(有可能已经有人实现了相关的转换方式)

       但并不影响Java文件的编写,只是此文件是通过Protobuf工具生成,生成的java文件拷贝到项目中即可使用。

工具安装方式,首先进行官网Protobuf生成工具下载

Windows

1,下载 protoc-3.12.1-win64.zip

2,下载完成后进行解压目录(C:\Steven\Protobuf\protoc-3.12.1-win64)

此时已经可以通过工具进行java代码生成了

3,进入bin目录(C:\Steven\Protobuf\protoc-3.12.1-win64\bin)

4,执行指令 ,验证Protobuf工具正常与否

protoc --version

说明可以使用了,当然可以将(C:\Steven\Protobuf\protoc-3.12.1-win64\bin)

设置为环境变量,

否则到其他文件夹下,会找不到指令

5,执行指令,完成java文件的生成

protoc person.proto --java_out=.

这样会成功生成java文件(C:\Steven\Protobuf\com\steven\entity\PersonProtobuf.java)

有没有发现,这里的包名+类名,就是proto文件中定义的,如果不写,将默认为.proto文件名称为类名

// 指定转换后java类的包名
option java_package = "com.steven.entity";
// 指定转换后java类名
option java_outer_classname = "PersonProtobuf";

注:如果报如下错误,只要命令执行的路径为person.proto文件路径就可以解决了!

Mac

需要下载文件protobuf-java-3.12.1.tar.gz

解压后,进入文件夹protobuf-java-3.12.1执行

make
make install

然后就可以通过上面一样的指令执行,进行java代码生成了!

使用生成的Java文件进行Http数据传输

1,新建Java项目protobuf,将java文件拷贝到相应的包路径下;

此时需要引入protobuf-java的包,否则会发生类找不到的异常。

注意:最好引入的protobuf-java jar 版本与工具的版本一致,至少一个大版本(3.12.x),如下:

<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
	<groupId>com.google.protobuf</groupId>
	<artifactId>protobuf-java</artifactId>
	<version>3.12.0</version>
</dependency>

2,Http传输解析Person Protobuf

思路:

客户端:生成Person实体内容,通过http,将Protobuf二进制流传输到服务端

服务端:服务端将传输内容进行解析,进行业务逻辑处理,并将处理后的Person同样的方式返回给客户端

代码可以如下编写:

package com.steven;

import com.steven.entity.PersonProtobuf;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

public class ProtobufTest extends TestCase {
    public ProtobufTest(String testName) {
        super(testName);
    }

    public static Test suite() {
        return new TestSuite(ProtobufTest.class);
    }


    public void testPersonProtobuf() throws Exception {

        PersonProtobuf.Person.PhoneNumber phoneNumber = PersonProtobuf.Person.PhoneNumber.newBuilder()
                .setNumber("13136056132")
                .setType(PersonProtobuf.Person.PhoneType.MOBILE)
                .build();

        PersonProtobuf.Person personRequest = PersonProtobuf.Person.newBuilder()
                .setId(1)
                .setName("Steven")
                .setEmail("[email protected]")
                .addPhone(phoneNumber)
                .build();

        System.out.println("请求实体:");
        System.out.println(personRequest);

        // 进行Http传输byte字节流
        byte[] bytes = personRequest.toByteArray();


        // http sent protobuf and get person protobuf

        InputStream inputStream = new ByteArrayInputStream(bytes);

        PersonProtobuf.Person personResponse = PersonProtobuf.Person.parseFrom(inputStream);

        System.out.println("响应实体:");
        System.out.println(personResponse);

    }
}

执行结果如下:

name: "Steven"
id: 1
email: "[email protected]"
phone {
  number: "13136056132"
  type: MOBILE
}

响应实体:
name: "Lisa"
id: 1
email: "[email protected]"
phone {
  number: "13186056360"
  type: MOBILE
}

请求实体和响应实体需要根据业务进行定义和使用,整体的实现可能有差别,但整体的思路和步骤就是如此,是不是很简单!

Http方式的数据传输

本文采用Apache http工具包

引入Jar包

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.8</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.5</version>
</dependency>

代码改写为:

package com.steven;

import com.steven.entity.PersonProtobuf;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.http.HttpEntity;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ProtobufTest extends TestCase {
    public ProtobufTest(String testName) {
        super(testName);
    }

    public static Test suite() {
        return new TestSuite(ProtobufTest.class);
    }


    public void testPersonProtobuf() throws Exception {

        PersonProtobuf.Person.PhoneNumber phoneNumber = PersonProtobuf.Person.PhoneNumber.newBuilder()
                .setNumber("13136056132")
                .setType(PersonProtobuf.Person.PhoneType.MOBILE)
                .build();

        PersonProtobuf.Person personRequest = PersonProtobuf.Person.newBuilder()
                .setId(1)
                .setName("Steven")
                .setEmail("[email protected]")
                .addPhone(phoneNumber)
                .build();

        System.out.println("请求实体:");
        System.out.println(personRequest);

        // 进行Http传输byte字节流
        byte[] bytes = personRequest.toByteArray();


        // http sent protobuf and get person protobuf
        HttpPost post = new HttpPost("http://www.steven.com/protobuf");
        // 建立一个NameValuePair数组,用于存储欲传送的参数
        post.setHeader("Content-type", "application/x-protobuf ");
        // 请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                // 与服务器连接超时时间:httpClient 会创建一个异步线程用以创建socket连接,此处设置该socket的连接超时时间
                .setConnectTimeout(1000)
                // socket 读数据超时时间:从服务器获取响应数据的超时时间
                .setSocketTimeout(10000).build();
        post.setConfig(requestConfig);

        ByteArrayEntity bae = new ByteArrayEntity(bytes);
        post.setEntity(bae);

        String data = HttpClients.createDefault().execute(post, generalResponseHandler());

        PersonProtobuf.Person personResponse = PersonProtobuf.Person.parseFrom(data.getBytes("ISO-8859-1"));

        System.out.println("响应实体:");
        System.out.println(personResponse);

    }

    private static ResponseHandler<String> generalResponseHandler() {
        return new BasicResponseHandler() {
            @Override
            public String handleEntity(HttpEntity entity) throws IOException {
                return EntityUtils.toString(entity, "ISO-8859-1");
            }
        };
    }
}

收尾

       在开发实践中,时不时会遇到Protobuf方式的数据传输,是否通过本文,可以更好的了解如何去使用这样的一种开发方式,在此预祝大家学习愉快!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章