目錄
前言
在現有的項目開發中,應用之間大多都再使用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
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方式的數據傳輸,是否通過本文,可以更好的瞭解如何去使用這樣的一種開發方式,在此預祝大家學習愉快!!!