proto的介绍和基础使用
內(nèi)容摘抄自書籍《Netty redis zookeeper高并發(fā)實戰(zhàn)》
Protobuf使用
proto文件來預(yù)先定義的消息格式。數(shù)據(jù)包是按照proto文件所定義的消息格式完成二進制碼流的編碼和解碼。proto文件,簡單地說,就是一個消息的協(xié)議文件,這個協(xié)議文件的后綴文件名為“.proto”。 作為演示,下面介紹一個非常簡單的proto文件:僅僅定義一個消息結(jié)構(gòu)體,并且該消息結(jié)構(gòu)體也非常簡單,僅包含兩個字段。實例如下:
// [開始頭部聲明] syntax = "proto3"; packagecom.crazymakercircle.netty.protocol; // [結(jié)束頭部聲明] // [開始 java選項配置] option java_package = "com.crazymakercircle.netty.protocol"; option java_outer_classname = "MsgProtos"; // [結(jié)束 java選項配置] // [開始消息定義] message Msg { uint32 id = 1; //消息IDstring content = 2;//消息內(nèi)容 } // [結(jié)束消息定義]例子:
syntax = "proto3"; message Model {int64 id = 1;string action = 2;string content = 3;string sender = 4;string receiver = 5;string extra = 6;string title = 7;string format = 8;int64 timestamp = 9; }注:在idea中下載protobuf插件即可以高亮proto文件
在“.proto”文件的頭部聲明中,需要聲明“.proto”所使用的Protobuf協(xié)議版本,這里使用的是"proto3"。也可以使用舊一點的版本"proto2",兩個版本的消息格式有一些細(xì)微的不同。
默認(rèn)的協(xié)議版本為"proto2"。
Protobuf支持很多語言,所以它為不同的語言提供了一些可選的聲明選項,選項的前面有option關(guān)鍵字。
“java_package”選項的作用為:在生成“proto”文件中消息的POJO類和Builder(構(gòu)造者)的Java代碼時,將Java代碼放入指定的package中。
“java_outer_classname”選項的作用為:在生成“proto”文件所對應(yīng)Java代碼時,所生產(chǎn)的Java外部類的名稱。 在“proto”文件中,使用message這個關(guān)鍵字來定義消息的結(jié)構(gòu)體。在生成“proto”對應(yīng)的Java代碼時,
每個具體的消息結(jié)構(gòu)體都對應(yīng)于一個最終的Java POJO類。消息結(jié)構(gòu)體的字段對應(yīng)到POJO類的屬性。也就是說,每定義一個“message”結(jié)構(gòu)體相當(dāng)于聲明一個Java中的類。并且message中可以內(nèi)嵌message,就像java的內(nèi)部類一樣。 每一個消息結(jié)構(gòu)體可以有多個字段。定義一個字段的格式,簡單來說就是“類型名稱=編號”。
例如“string content=2;”,表示該字段是string類型,名為content,序號為2。字段序號表示為:在Protobuf數(shù)據(jù)包的序列化、反序列化時,該字段的具體排序。 在每一個“.proto”文件中,可以聲明多個“message”。大部分情況下,會把有依賴關(guān)系或者包含關(guān)系的message消息結(jié)構(gòu)體寫入一個.proto文件。將那些沒有關(guān)聯(lián)關(guān)系的message消息結(jié)構(gòu)體,分別寫入不同的文件,這樣便于管理。
Maven插件生成POJO和Builder
使用命令行生成Java類的操作比較煩瑣。另一種更加方便的方式是:使用protobuf-maven-plugin插件,它可非常方便地生成消息的POJO類和Builder(構(gòu)造者)類的Java代碼。在Maven的pom文件中增加此plugin插件的配置項,具體如下:
<plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.5.0</version><extensions>true</extensions><configuration><!--proto文件路徑--><protoSourceRoot>${project.basedir}/protobuf</protoSourceRoot><!--目標(biāo)路徑--><outputDirectory>${project.build.sourceDirectory}</outputDirectory><!--設(shè)置是否在生成Java文件之前清空outputDirectory的文件--><clearOutputDirectory>false</clearOutputDirectory><!--臨時目錄--><temporaryProtoFileDirectory>${project.build.directory}/protoc-temp</temporaryProtoFileDirectory><!--protoc可執(zhí)行文件路徑--><protocExecutable>${project.basedir}/protobuf/protoc3.6.1.exe</protocExecutable></configuration><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions> </plugin>protobuf-maven-plugin插件的配置項,具體介紹如下:
·protoSourceRoot:“proto”消息結(jié)構(gòu)體文件的路徑。
·outputDirectory:生成的POJO類和Builder類的目標(biāo)路徑。
·protocExecutable:Java代碼生成工具的protoc3.6.1.exe可執(zhí)行文件的路徑。
配置好之后,執(zhí)行插件的compile命令,Java代碼就利索生成了。或者在Maven的項目編譯時,POJO類和Builder類也會自動生成。
1.使用Builder構(gòu)造者,構(gòu)造POJO消息對象
package com.crazymakercircle.netty.protocol; //... public class ProtobufDemo {public static MsgProtos.MsgbuildMsg() {MsgProtos.Msg.BuilderpersonBuilder = MsgProtos.Msg.newBuilder();personBuilder.setId(1000);personBuilder.setContent("瘋狂創(chuàng)客圈:高性能學(xué)習(xí)社群");MsgProtos.Msg message = personBuilder.build(); return message;}//….. }Protobuf為每個message消息結(jié)構(gòu)體生成的Java類中,包含了一個POJO類、一個Builder類。
構(gòu)造POJO消息,首先需要使用POJO類的newBuilder靜態(tài)方法獲得一個Builder構(gòu)造者。每一個POJO字段的值,需要通過Builder構(gòu)造者的setter方法去設(shè)置。注意,消息POJO對象并沒有setter方法。字段值設(shè)置完成之后,使用構(gòu)造者的build()方法構(gòu)造出POJO消息對象。
2.序列化serialization & 反序列化Deserialization的方式
一 獲得消息POJO的實例之后,可以通過多種方法將POJO對象序列化成二進制字節(jié),或者反序列化。下面是方式一:
package com.crazymakercircle.netty.protocol; //... public class ProtobufDemo { //第1種方式:序列化 serialization &反序列化 Deserialization@Testpublic void serAndDesr1() throws IOException {MsgProtos.Msg message = buildMsg();//將Protobuf對象序列化成二進制字節(jié)數(shù)組byte[] data = message.toByteArray();//可以用于網(wǎng)絡(luò)傳輸,保存到內(nèi)存或外存ByteArrayOutputStreamoutputStream = new ByteArrayOutputStream();outputStream.write(data);data = outputStream.toByteArray();//二進制字節(jié)數(shù)組反序列化成Protobuf對象MsgProtos.MsginMsg = MsgProtos.Msg.parseFrom(data);Logger.info("id:=" + inMsg.getId());Logger.info("content:=" + inMsg.getContent());} //…. }這種方式通過調(diào)用POJO對象的toByteArray()方法將POJO對象序列化成字節(jié)數(shù)組。通過調(diào)用parseFrom(byte[] data)方法,Protobuf也可以從字節(jié)數(shù)組中重新反序列化得到POJO新的實例。
3.序列化serialization & 反序列化Deserialization的方式二
package com.crazymakercircle.netty.protocol; //... public class ProtobufDemo {//…//第2種方式:序列化 serialization &反序列化 Deserialization@Testpublic void serAndDesr2() throws IOException {MsgProtos.Msg message = buildMsg();//序列化到二進制碼流ByteArrayOutputStreamoutputStream = new ByteArrayOutputStream();message.writeTo(outputStream);ByteArrayInputStreaminputStream =new ByteArrayInputStream(outputStream.toByteArray());//從二進碼流反序列化成Protobuf對象MsgProtos.MsginMsg = MsgProtos.Msg.parseFrom(inputStream);Logger.info("id:=" + inMsg.getId());Logger.info("content:=" + inMsg.getContent());} //…. }這種方式通過調(diào)用POJO對象的writeTo(OutputStream)方法將POJO對象的二進制字節(jié)寫出到輸出流。通過調(diào)用parseFrom(InputStream)方法,Protobuf從輸入流中讀取二進制碼流重新反序列化,得到POJO新的實例。 在阻塞式的二進制碼流傳輸應(yīng)用場景中,這種序列化和反序列化的方式是沒有問題的。例如,可以將二進制碼流寫入阻塞式的Java OIO套接字或者輸出到文件。但是,這種方式在異步操作的NIO應(yīng)用場景中,存在著粘包/半包的問題。
4.序列化serialization &反序列化Deserialization的方式三
package com.crazymakercircle.netty.protocol; //... public class ProtobufDemo {//… //第3種方式:序列化 serialization &反序列化 Deserialization//帶字節(jié)長度:[字節(jié)長度][字節(jié)數(shù)據(jù)],解決粘包/半包問題@Testpublic void serAndDesr3() throws IOException {MsgProtos.Msg message = buildMsg();//序列化到二進制碼流ByteArrayOutputStreamoutputStream = new ByteArrayOutputStream();message.writeDelimitedTo(outputStream);ByteArrayInputStreaminputStream = new ByteArrayInputStream(outputStream.toByteArray());//從二進碼流反序列化成Protobuf對象MsgProtos.MsginMsg = MsgProtos.Msg.parseDelimitedFrom(inputStream);Logger.info("id:=" + inMsg.getId());Logger.info("content:=" + inMsg.getContent());} }這種方式通過調(diào)用POJO對象的writeDelimitedTo(OutputStream)方法在序列化的字節(jié)碼之前添加了字節(jié)數(shù)組的長度。這一點類似于前面介紹的Head-Content協(xié)議,只不過Protobuf做了優(yōu)化,長度的類型不是固定長度的int類型,而是可變長度varint32類型。 反序列化時,調(diào)用parseDelimitedFrom(InputStream)方法。Protobuf從輸入流中先讀取varint32類型的長度值,然后根據(jù)長度值讀取此消息的二進制字節(jié),再反序列化得到POJO新的實例。 這種方式可以用于異步操作的NIO應(yīng)用場景中,解決了粘包/半包的問題。
總結(jié)
以上是生活随笔為你收集整理的proto的介绍和基础使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云文件上传工具类
- 下一篇: jquery+原生js模拟淘宝输入框下拉