Protocol Buffers 是一種與語言、平臺無關,可擴充套件的序列化結構化資料的方法,常用於通訊協議,資料儲存等等。相較於 JSON、XML,它更小、更快、更簡單,因此也更受開發人員的青眯。
基本語法
syntax = “proto3”;
package model;
service MyServ {
rpc Query(Request) returns(Reply);
}
message Student {
int64 id = 1;
string name = 2;
int32 age = 3;
}
定義完 proto檔案後,生成相應語言的程式碼
protoc --proto_path=. --go_out=plugins=grpc,paths=source_relative:. xxxx.proto
--proto_path
或者 -I
引數用以指定所編譯原始碼(包括直接編譯的和被匯入的 proto 檔案)的搜尋路徑
--go_out
引數之間用逗號隔開,最後用冒號來指定程式碼目錄架構的生成位置 ,--go_out=plugins=grpc
引數來生成gRPC相關程式碼,如果不加plugins=grpc
,就只生成message
資料
eg:--go_out=plugins=grpc,paths=import:. 。注意一下 paths 引數,他有兩個選項,import 和 source_relative 。預設為 import ,代表按照生成的 go 程式碼的包的全路徑去建立目錄層級,source_relative 代表按照 proto 原始檔的目錄層級去建立 go 程式碼的目錄層級,如果目錄已存在則不用建立
protoc
是通過外掛機制實現對不同語言的支援。比如 --xxx_out
引數,那麼protoc將首先查詢是否有內建的xxx外掛,如果沒有內建的xxx外掛那麼將繼續查詢當前系統中是否存在protoc-gen-xxx命名的可執行程式。
例如,生成 c++程式碼
protoc -I . --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` --cpp_out=. *.proto
匯入依賴的proto
檔案
為了方便,會把公共的一些欄位放到一個proto
檔案裡,如果有需要,就把這個proto
檔案impot
進去,比如,我現在的組織結構好下
common.proto
檔案裡只有個簡單的message
syntax = "proto3";
package protos;
option go_package = "protos";
option java_package = "com.proto";
message Result {
string code = 1;
string desc = 2;
bytes data = 3;
}
目錄api
裡student_api.proto
在這個檔案裡,我們匯入了common.proto
,還有其他需要的檔案
syntax = "proto3";
package api;
option go_package = "protos/api";
option java_package = "com.proto.api";
import "protos/common.proto";
import "protos/model/students.proto";
import "google/protobuf/empty.proto";
service StudentSrv {
rpc NewStudent(model.Student) returns (protos.Result);
rpc StudentByID(QueryStudent) returns (QueryStudentResponse);
rpc AllStudent(google.protobuf.Empty) returns(stream QueryStudentResponse);
rpc StudentInfo(stream QueryStudent) returns(stream QueryStudentResponse);
}
message QueryStudent {
int64 id = 1;
}
message QueryStudentResponse {
repeated model.Student studentList = 1;
}
在執行protoc
的時候,我們要指定這些需檔案的查詢路徑,在專案的根目錄裡執行protoc
進行程式碼生成
protoc -I=. --go_out=plugins=grpc:. --go_opt=paths=source_relative protos/api/*.proto
上面的-I
指定了當前目錄,就是說可以從當前目錄開始找proto
檔案
protoc 生成了什麼
以 student.proto
為例
syntax = "proto3";
package model;
option go_package = "protos/model";
option java_package = "com.proto.model";
message Student {
int64 id = 1;
string name = 2;
int32 age = 3;
}
message StudentList {
string class = 1;
repeated Student students = 2;
string teacher = 3;
repeated int64 score = 4;
}
執行完protoc
後,大概看一下生成的的go
檔案
type Student struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,3,opt,name=age,proto3" json:"age,omitempty"`
}
state
儲存 proto檔案的反射資訊 sizeCache
序列化的資料總長度 unknownFields
不能解析的欄位
剩下的欄位是我們message
裡定義的資訊,主要看一下tag
資訊
protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"
,說明這個欄位是protobuf的varint
型別,index
為1
name為id
,使用proto3
協議
還有一個byte
陣列的file_protos_model_students_proto_rawDesc
一眼看上去就有點蒙,這一坨是什麼?開源的好處就是,我可以很清楚的看清他是做什麼的,
這個file_protos_model_students_proto_rawDesc
是proto
裡資料的描述資訊。如 proto
的路徑、包名稱,message
資訊等等。
file_protos_model_students_proto_rawDesc
描述資訊有什麼用呢?
當我們在執行proto.Marshal
的時候,會對傳入的引數Message
進行驗證,比如每個message
欄位的index
、資料型別,是否和file_protos_model_students_proto_rawDesc
一致。如果不一致就說明是有問題的。
protobuf支援的資料型別
protobuf
目前支援這5種資料型別,還有2個是已經廢棄了。protobuf
是語言無關的,也就是說,無論具體的語言支援哪些資料型別,在marshal
的時候都要轉換成這幾種,在unmarshal
的時候再轉換成具體語言的型別
我們把一個結構轉換成json
Student {
Id: 1,
Name: "孫悟空",
Age: 300,
}
{
"id": 1,
"name": "孫悟空",
"age": 300
}
轉換成 protobuf
資料格式
1000 1 10010 1001 11100101 10101101 10011001 11100110 10000010 10011111 11100111 10101001 10111010 11000 10101100 10
轉換成十進位制
8 1 8 9 229 173 153 230 130 159 231 169 186 24 172 2
json
一眼就能看懂是什麼 ,protobuf
資料格式看不明白,下面來解釋這些資料都是什麼。
index 和型別
先說一下第一個byte 1000
這個表示的是欄位的index
和型別,
protobuf
把一個欄位的 index 和型別放在了一起
(field_number << 3) | wire_type
最後3個bit為型別,前面的bit為index
0000 1000
首位為標識位,index為 1 後三位為wire_type:0(Varint型別)再比如 10010
index: 2 wire_type: 2(Length-delimited型別)
Varint型別
Varint
資料型別,最高位(msb)標誌位,為1說明後面還有byte,0說明後面沒有byte,使用後面的7個Bit位儲存數值
Id: 1
protobuf對應的資料是0000 0001
這個很好理解
Age: 300
protobuf對應的資料是1010 1100 0000 0010
,這個是怎麼計算的呢?
protobuf資料 1010 1100 0000 0010
去掉最高位 010 1100 000 0010
連線剩餘 0100101100
計算 256 + 32 + 8 + 4 = 300
Length-delimited 型別
字串記憶體的表現形式protobuf
一個漢字佔3個byte
“孫悟空”記憶體的資料
11100101 10101101 10011001 11100110 10000010 10011111 11100111 10101001 10111010
“孫悟空”前面的一個byte:1001
這個數值有什麼意義?對,字串長度 9
Length-delimited
型別的資料第一個byte是資料的長度,後面是具體的資料資訊,資料大同小異