一、Protocol buffers簡(jiǎn)述
特點(diǎn)
Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.
- 語(yǔ)言中立,平臺(tái)中立
- 序列化結(jié)構(gòu)化數(shù)據(jù)
protocol核心學(xué)習(xí)點(diǎn)
- protocol buffer 語(yǔ)法https://developers.google.com/protocol-buffers/docs/proto
- proto3語(yǔ)法https://developers.google.com/protocol-buffers/docs/proto3
- buffer如何進(jìn)行序列化以及反序列化https://developers.google.com/protocol-buffers/docs/encoding
優(yōu)勢(shì)
- simpler(簡(jiǎn)單)
- smaller(文件小)
- faster(序列化與反序列化速度快)
- 支持多種語(yǔ)言,相對(duì)于xml更易于程序處理
主要是由于采用protocol方式生成的binary format文件相比于xml格式的更小,序列化,反序列化,網(wǎng)絡(luò)傳輸過(guò)程中有更大優(yōu)勢(shì)。
歷史演變主要解決的問(wèn)題
- 協(xié)議不同版本的字段變換時(shí)帶來(lái)的兼容性問(wèn)題
- 協(xié)議能夠帶來(lái)了更好的自描述,能夠被不同語(yǔ)言處理
演變歷程
- 自動(dòng)產(chǎn)生序列化與反序列化代碼-》如何使用?
- 利用protocol buffer自描述能力,用于大數(shù)據(jù)存儲(chǔ)-》如何存儲(chǔ),效率問(wèn)題如何?
- 能夠使用編譯器能夠自動(dòng)生成服務(wù)端stub程序-》如何生成?
二、Proto3 buffer語(yǔ)法格式
暫時(shí)跳過(guò)proto2的語(yǔ)法格式
2.1 Defining A Message Type
如何在proto文件中定義一個(gè)消息類型?
syntax = "proto3"
message request {
string name = 1;
string age=2;
int32 page_numer = 3;
int32 result_per_page = 4;
}
proto messge主要有field types, field name以及field number構(gòu)成。
其中field number 【1-15】區(qū)間占用One Byte,【16-2047】占用兩個(gè)bytes,所以在message中fields個(gè)數(shù)不多的情況,盡量使用1-15之內(nèi)的數(shù)值。
field number允許的值范圍為【1-2的29次方-1(536870911)】,其中【19000-19999】為保留數(shù)值。
Adding Comments
proto use c/c++ style comments, //for line comments, /.../for block comment
Reserved Fields
message ReservedTest{
reserved 2, 6, 10 to max;
reserved "name", "age";
}
Note:不能在同一行混用field name與number,必須分開(kāi)保留。
2.2 編譯proto文件
對(duì)于一個(gè)初學(xué)者來(lái)說(shuō),剛開(kāi)始肯定會(huì)拿到一個(gè)proto文件,拿到此文件后如何下手?大致步驟如下:
-
安裝編譯環(huán)境,protocol buffer compiler能夠根據(jù)不同的客戶端語(yǔ)言,生成對(duì)應(yīng)的代碼,規(guī)則如下
For C++, the compiler generates a .h and .cc file from each .proto, with a class for each message type described in your file.
For Java, the compiler generates a .java file with a class for each message type, as well as a special Builder classes for creating message class instances.
Python is a little different – the Python compiler generates a module with a static descriptor of each message type in your .proto, which is then used with a metaclass to create the necessary Python data access class at runtime.
For Go, the compiler generates a .pb.go file with a type for each message type in your file.
For Ruby, the compiler generates a .rb file with a Ruby module containing your message types.
For Objective-C, the compiler generates a pbobjc.h and pbobjc.m file from each .proto, with a class for each message type described in your file.
For C#, the compiler generates a .cs file from each .proto, with a class for each message type described in your file.
For Dart, the compiler generates a .pb.dart file with a class for each message type in your file 運(yùn)行編譯指令
2.3 Scalar Value Types
描述protocol文件的field types如何與其他不同語(yǔ)言的數(shù)據(jù)類型進(jìn)行一一映射。
具體映射關(guān)系參考protocol數(shù)據(jù)類型映射表
protocol中定義的數(shù)據(jù)類型:
double,float,int32,int64,uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, string, bytes
對(duì)于不同數(shù)據(jù)類型,不同操作數(shù)據(jù)類型,在數(shù)據(jù)序列化與反序列化的時(shí)候,會(huì)對(duì)不同數(shù)據(jù)類型進(jìn)行一定的優(yōu)化。
2.4 Default values
在message被反序列化的時(shí)候,如果缺少特定field的描述,反序列化時(shí)就會(huì)設(shè)置對(duì)應(yīng)的默認(rèn)值,規(guī)則如下:
- For strings, the default value is the empty string.—空字符串
- For bytes, the default value is empty bytes.—空字節(jié)
- For bools, the default value is false.-錯(cuò)
- For numeric types, the default value is zero.-0
- For enums, the default value is the first defined enum value, which must be 0.-聯(lián)合第一個(gè)值必須為0
- For message fields, the field is not set. Its exact value is language-dependent. See the generated code guide for details.
2.5 Enumerations
聯(lián)合是protocol中相對(duì)復(fù)雜的type,例如
message request {
string name = 1;
int age = 2;
enum Sex {
MALE = 0;
FEMALE = 1;
OTHER = 2
}
Sex gender = 3;
}
在protocol中對(duì)enum的第一個(gè)元素作了特別的規(guī)定,必須為值為0,主要是為了解決enum的默認(rèn)值問(wèn)題以及兼容proto2協(xié)議。
聯(lián)合字段別名
示例:
message request {
string name = 1;
int age = 2;
enum Sex {
option allow_alias = true;
MALE = 0;
FEMALE = 1;
MAN = 1;
OTHER = 2
}
Sex gender = 3;
}
允許聯(lián)合字段別名在Java或者C語(yǔ)言中比較少見(jiàn),protocol默認(rèn)也不允許開(kāi)啟聯(lián)合別名,如果要開(kāi)啟必須通過(guò)手動(dòng)打開(kāi)配置選項(xiàng)
2.6 Using Other Message Types
protocol文件之間如何相互引用已經(jīng)定義好的Message?如何通過(guò)Message嵌套引用形成消息結(jié)構(gòu)層次?
示例:
message Response {
int http_code = 1;
string ret_msg = 2;
repeated Device data = 3;
}
message Result {
string device_uuid = 1;
string device_name = 2;
float longitude = 3;
float latitude = 4;
}
一般來(lái)說(shuō),在實(shí)際的代碼開(kāi)發(fā)過(guò)程中,盡量將Message顆粒度進(jìn)行拆分,便于在項(xiàng)目中相互調(diào)用。
importing definitions
protocol也支持導(dǎo)入其他proto文件,通過(guò)proto文件導(dǎo)入方式,能夠通過(guò)合并導(dǎo)入proto文件形成新的proto文件,可以對(duì)不通版本,用戶暴露不一樣的proto文件。
2.7 Unknown Fields
對(duì)于解析端,如果解析的數(shù)據(jù)流中包含一些未知字段,通常來(lái)說(shuō)解析時(shí)候把未知字段給忽略了,但是在protocol3.5及以后的版本中,未知字段在解析時(shí)候會(huì)被保留而不是丟棄。
2.8 Any
protocol中提供了一種特殊的消息類型Any。 Any的意思為:字段可以為任何類型,在序列化時(shí)候就將此字段序列化為bytes。
示例:
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
在使用Any消息類型的時(shí)候,必須引入google的any這個(gè)proto文件。對(duì)于Any的使用場(chǎng)景需要收集總結(jié)?
2.9 Oneof
Oneof用于標(biāo)記消息中某些字段同時(shí)最多只有一個(gè)能夠同時(shí)存在,經(jīng)常用于標(biāo)記互斥的字段。
示例:
message Person {
oneof contact {
string mobile = 4;
string email = 9;
}
}
Oneof Features
注意在實(shí)際消息對(duì)象賦值的過(guò)程中,oneof特性帶來(lái)值覆蓋問(wèn)題,最終值是最后一個(gè)oneof字段,oneof字段不允許使用repeated。
oneof使用案例
2.10 Maps
Maps也是最常見(jiàn)的數(shù)據(jù)類型。
示例
map<string, Person> class = 2;
protocol對(duì)map做了以下限定
- key必須為inegral以及string,value可以為除了map后的任何類型
- map不能用repeated來(lái)進(jìn)行修飾
- 不允許有重復(fù)的key
- 對(duì)于只有key沒(méi)有value情況,不同語(yǔ)言處理方式不一樣
2.11 Packages
protocol為了防止定義的消息類型沖突,也引入了package概念,類似于java,c++等語(yǔ)言,protocol的包在轉(zhuǎn)換為不同語(yǔ)言實(shí)現(xiàn)時(shí),會(huì)有相關(guān)轉(zhuǎn)換關(guān)系。
定義一個(gè)包的消息:
package com.dwx;
message Person {
}
引入一個(gè)包對(duì)象
message Class {
com.dwx.Person stu = 1;
}
在proto文件轉(zhuǎn)換為java文件時(shí),默認(rèn)用定義的package名來(lái)作為java的包名,如果想改變?cè)撘?guī)律,可以使用配置項(xiàng)
option java_package = "com.xx.xx"
2.12 Defining Services
proto文件中除了定義消息類型,另一個(gè)重要任務(wù)就是服務(wù)接口。編譯器會(huì)根據(jù)選擇的語(yǔ)言生成相應(yīng)的接口代碼以及存根代碼。
常見(jiàn)的service示例
service SearchPerson {
rpc Search(Person) returns(Class);
}
不同的編譯插件可以快速生成對(duì)應(yīng)的interface以及stub代碼,需要深入研究maven或者gradle方式下如何集成相關(guān)插件,運(yùn)行指令等。
2.13 Options
proto buffer提供可選項(xiàng)參數(shù),用于改變編譯的一些默認(rèn)行為,目前主要有針對(duì)java或者移動(dòng)端的一些優(yōu)化配置,匯總?cè)缦?
-
java_package: 改變java的默認(rèn)package名
option java_package = "xx.xx.xx"
-
java_ multiple_files:是否將messages,enums以及services作為內(nèi)部類
option java_ multiple_files = true
-
java_outer _classname:指定outerclass名字,默認(rèn)為proto文件名
option java_outer _classname = "PersonStu"
optimize_ for:可以被設(shè)置為SPEED,CODE_SIZE,LITE_RUNTIME。通過(guò)設(shè)置優(yōu)化屬性,代碼產(chǎn)生器會(huì)針對(duì)不同的語(yǔ)言進(jìn)行各種優(yōu)化。
Custom Options:可以自己定義優(yōu)化特性。
3 如何生成class
3.1 環(huán)境準(zhǔn)備
安裝編譯環(huán)境,windows平臺(tái)下可以選擇windows protoc 3.6.1安裝文件
配置系統(tǒng)環(huán)境變量,在path中添加bin路徑,例如 D:\IDE\protoc-3.6.0-win32\bin
在cmd命令行下,輸入protoc --version,回顯libprotoc XXX, XXX為安裝的版本號(hào)
3.2 編寫(xiě).proto文件
syntax = "proto3"
message PersonRequest{
string name = 1; //姓名
uint32 age = 2; //年齡
string birth_day = 3; //出生日期
string mobile = 4; //手機(jī)號(hào)
enum Sex {
Female = 0;
Male = 1;
Other = 2;
}
Sex gender = 5; //define sex
}
3.3 命令行編譯
指令格式如下:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
其中各個(gè)參數(shù)的含義如下:
- --proto_path:簡(jiǎn)寫(xiě)-I, 為import文件的路徑,缺省為當(dāng)前文件路徑。
- --**_output,主要為編譯器根據(jù)不同語(yǔ)言產(chǎn)生的代碼文件,如果DST——DIR是.zip或者.jar,編譯器會(huì)根據(jù)要求生成相應(yīng)的文件
- 提供一個(gè)或者多個(gè)文件作為輸入文件,文件默認(rèn)也是參考當(dāng)前的路徑。
3.4 案例演示
- 編寫(xiě)Person.proto文件
- 運(yùn)行protoc --java_out ./rpc/out/person.jar --go_out .\rpc\out\go\ .\rpc\Person.proto,報(bào)"--go_out:protoc-gen-go:系統(tǒng)找不到指定的文件",主要是編譯器中不包含golang的代碼生成器,刪除掉go的out文件即可。
- 重新運(yùn)行protoc --java_ out ./rpc/out/person.jar .\rpc\Person.proto,即可生成對(duì)應(yīng)的jar文件,主要out_dst文件夾必須為已存在,未存在的目錄代碼生成器不會(huì)自動(dòng)創(chuàng)建。
- 可以通過(guò)修改配置項(xiàng)來(lái)調(diào)整生成的jar的內(nèi)容
4 code style
代碼約定:
- Message名:用駝峰,第一個(gè)字母為大寫(xiě),如PersonRequest
- 字段名字用小寫(xiě),名字用下劃線之間分割,例如:birth_date
- 聯(lián)合全部用大寫(xiě)
- service名字用大寫(xiě),service中的方法名也用大寫(xiě)首字母+駝峰命名