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方式的數據傳輸,是否通過本文,可以更好的瞭解如何去使用這樣的一種開發方式,在此預祝大家學習愉快!!!

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