go protobuf?詳解
protobuf簡介
Protobuf是Protocol Buffers的簡稱,它是Google公司開發(fā)的一種數(shù)據(jù)描述語言,是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,或者說序列化 。它很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式??捎糜谕ㄓ崊f(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)、平臺無關(guān)、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)格式。目前提供了 C++、Java、Python 三種語言的 API。
- protobuf是類似與json一樣的數(shù)據(jù)描述語言(數(shù)據(jù)格式)
- protobuf非常適合于RPC數(shù)據(jù)交換格式
注意:protobuf
本身并不是和gRPC
綁定的。它也可以被用于非RPC場景,如存儲等
protobuf的優(yōu)劣勢
1)優(yōu)勢:
- 序列化后體積相比Json和XML很小,適合網(wǎng)絡傳輸
- 序列化反序列化速度很快,快于Json的處理速度
- 消息格式升級和兼容性還不錯
- 支持跨平臺多語言
2)劣勢:
- 應用不夠廣(相比xml和json)
- 二進制格式導致可讀性差
- 缺乏自描述
protoc安裝(windows)
protoc就是protobuf的編譯器,它把proto文件編譯成不同的語言
下載安裝protoc編譯器(protoc)
下載protobuf:https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-win64.zip
解壓后,將目錄中的 bin 目錄的路徑添加到系統(tǒng)環(huán)境變量,然后打開cmd輸入protoc
查看輸出信息,此時則安裝成功
安裝protocbuf的go插件(protoc-gen-go)
由于protobuf并沒直接支持go語言需要我們手動安裝相關(guān)插件
protocol buffer編譯器需要一個插件來根據(jù)提供的proto文件生成 Go 代碼,Go1.16+要使用下面的命令安裝插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest // 目前最新版是v1.3.0
安裝grpc(grpc)
go get -u -v google.golang.org/grpc@latest // 目前最新版是v1.53.0
安裝grpc的go插件(protoc-gen-go-grpc)
說明:在google.golang.org/protobuf
中,protoc-gen-go
純粹用來生成pb序列化相關(guān)的文件,不再承載gRPC代碼生成功能,所以如果要生成grpc相關(guān)的代碼需要安裝grpc-go相關(guān)的插件:protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest // 目前最新版是v1.3.0
protobuf語法
protobuf語法
類型:類型不僅可以是標量類型(int
、string
等),也可以是復合類型(enum
等),也可以是其他message
字段名:字段名比較推薦的是使用下劃線/分隔名稱
字段編號:一個message內(nèi)每一個字段編號都必須唯一的,在編碼后其實傳遞的是這個編號而不是字段名
字段規(guī)則:消息字段可以是以下字段之一
singular
:格式正確的消息可以有零個或一個字段(但不能超過一個)。使用 proto3 語法時,如果未為給定字段指定其他字段規(guī)則,則這是默認字段規(guī)則
optional
:與 singular
相同,不過可以檢查該值是否明確設(shè)置
repeated
:在格式正確的消息中,此字段類型可以重復零次或多次。系統(tǒng)會保留重復值的順序
map
:這是一個成對的鍵值對字段
保留字段:為了避免再次使用到已移除的字段可以設(shè)定保留字段。如果任何未來用戶嘗試使用這些字段標識符,編譯器就會報錯
簡單語法
proto文件基本語法
syntax = "proto3"; // 指定版本信息,不指定會報錯 package pb; // 后期生成go文件的包名 // message為關(guān)鍵字,作用為定義一種消息類型 message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 } ? enum test{ int32 age = 0; }
protobuf消息的定義(或者稱為描述)通常都寫在一個以 .proto 結(jié)尾的文件中:
- 第一行指定正在使用
proto3
語法:如果不這樣做,協(xié)議緩沖區(qū)編譯器將假定正在使用proto2(這也必須是文件的第一個非空的非注釋行) - 第二行package指明當前是pb包(生成go文件之后和Go的包名保持一致)
- message關(guān)鍵字定義一個Person消息體,類似于go語言中的結(jié)構(gòu)體,是包含一系列類型數(shù)據(jù)的集合。
- 許多標準的簡單數(shù)據(jù)類型都可以作為字段類型,包括
bool
,int32
,float
,double
,和string
- 也可以使用其他message類型作為字段類型。
- 許多標準的簡單數(shù)據(jù)類型都可以作為字段類型,包括
在message中有一個字符串類型的value成員,該成員編碼時用1代替名字。在json中是通過成員的名字來綁定對應的數(shù)據(jù),但是Protobuf編碼卻是通過成員的唯一編號來綁定對應的數(shù)據(jù),因此Protobuf編碼后數(shù)據(jù)的體積會比較小,能夠快速傳輸,缺點是不利于閱讀。
message常見的數(shù)據(jù)類型與go中類型對比
.proto類型 | Go類型 | 介紹 |
---|---|---|
double | float64 | 64位浮點數(shù) |
float | float32 | 32位浮點數(shù) |
int32 | int32 | 使用可變長度編碼。編碼負數(shù)效率低下——如果你的字段可能有負值,請改用sint32。 |
int64 | int64 | 使用可變長度編碼。編碼負數(shù)效率低下——如果你的字段可能有負值,請改用sint64。 |
uint32 | uint32 | 使用可變長度編碼。 |
uint64 | uint64 | 使用可變長度編碼。 |
sint32 | int32 | 使用可變長度編碼。符號整型值。這些比常規(guī)int32s編碼負數(shù)更有效。 |
sint64 | int64 | 使用可變長度編碼。符號整型值。這些比常規(guī)int64s編碼負數(shù)更有效。 |
fixed32 | uint32 | 總是四字節(jié)。如果值通常大于228,則比uint 32更有效 |
fixed64 | uint64 | 總是八字節(jié)。如果值通常大于256,則比uint64更有效 |
sfixed32 | int32 | 總是四字節(jié)。 |
sfixed64 | int64 | 總是八字節(jié)。 |
bool | bool | 布爾類型 |
string | string | 字符串必須始終包含UTF - 8編碼或7位ASCII文本 |
bytes | []byte | 可以包含任意字節(jié)序列 |
protobuff語法進階
message嵌套
messsage除了能放簡單數(shù)據(jù)類型外,還能存放另外的message類型:
syntax = "proto3"; // 指定版本信息,不指定會報錯 package pb; // 后期生成go文件的包名 // message為關(guān)鍵字,作用為定義一種消息類型 message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 // 定義一個message message PhoneNumber { string number = 1; int64 type = 2; } PhoneNumber phone = 3; }
message成員編號,可以不從1開始,但是不能重復,不能使用19000 - 19999
repeated關(guān)鍵字
repeadted關(guān)鍵字類似與go中的切片,編譯之后對應的也是go的切片,用法如下:
syntax = "proto3"; // 指定版本信息,不指定會報錯 package pb; // 后期生成go文件的包名 // message為關(guān)鍵字,作用為定義一種消息類型 message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 // 定義一個message message PhoneNumber { string number = 1; int64 type = 2; } repeated PhoneNumber phone = 3; }
默認值
解析數(shù)據(jù)時,如果編碼的消息不包含特定的單數(shù)元素,則解析對象對象中的相應字段將設(shè)置為該字段的默認值
不同類型的默認值不同,具體如下:
- 對于字符串,默認值為空字符串
- 對于字節(jié),默認值為空字節(jié)
- 對于bools,默認值為false
- 對于數(shù)字類型,默認值為零
- 對于枚舉,默認值是第一個定義的枚舉值,該值必須為0。
- repeated字段默認值是空列表
- message字段的默認值為空對象
enum關(guān)鍵字
在定義消息類型時,可能會希望其中一個字段有一個預定義的值列表
比如說,電話號碼字段有個類型,這個類型可以是,home,work,mobile
我們可以通過enum在消息定義中添加每個可能值的常量來非常簡單的執(zhí)行此操作。示例如下:
syntax = "proto3"; // 指定版本信息,不指定會報錯 package pb; // 后期生成go文件的包名 // message為關(guān)鍵字,作用為定義一種消息類型 message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 // 定義一個message message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phone = 3; } ? // enum為關(guān)鍵字,作用為定義一種枚舉類型 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; }
如上,enum的第一個常量映射為0,每個枚舉定義必須包含一個映射到零的常量作為其第一個元素。這是因為:
必須有一個零值,以便我們可以使用0作為數(shù)字默認值。
零值必須是第一個元素,以便與proto2語義兼容,其中第一個枚舉值始終是默認值。
enum還可以為不同的枚舉常量指定相同的值來定義別名。如果想要使用這個功能必須將allow_alias
選項設(shè)置為true,負責編譯器將報錯。示例如下:
syntax = "proto3"; // 指定版本信息,不指定會報錯 package pb; // 后期生成go文件的包名 // message為關(guān)鍵字,作用為定義一種消息類型 message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 // 定義一個message message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phone = 3; } ? // enum為關(guān)鍵字,作用為定義一種枚舉類型 enum PhoneType { // 如果不設(shè)置將報錯 option allow_alias = true; MOBILE = 0; HOME = 1; WORK = 2; Personal = 2; }
oneof關(guān)鍵字
如果有一個包含許多字段的消息,并且最多只能同時設(shè)置其中的一個字段,則可以使用oneof功能,示例如下:
message Person{ string name = 1; // 名字 int32 age = 2 ; // 年齡 //定義一個message message PhoneNumber { string number = 1; PhoneType type = 2; } ? repeated PhoneNumber phone = 3; oneof data{ string school = 5; int32 score = 6; } }
定義RPC服務
如果需要將message與RPC一起使用,則可以在.proto
文件中定義RPC服務接口,protobuf編譯器將根據(jù)你選擇的語言生成RPC接口代碼。示例如下:
//定義RPC服務 service HelloService { rpc Hello (Person)returns (Person); }
注意:默認protobuf編譯期間,不編譯服務,如果要想讓其編譯,需要使用gRPC
protobuf編譯
編譯器調(diào)用
protobuf 編譯是通過編譯器 protoc 進行的,通過這個編譯器,我們可以把 .proto 文件生成 go,Java,Python,C++, Ruby或者C# 代碼
可以使用以下命令來通過 .proto 文件生成go代碼(以及grpc代碼)
// 將當前目錄中的所有 .proto文件進行編譯生成go代碼 protoc --go_out=./ --go_opt=paths=source_relative *.proto
protobuf 編譯器會把 .proto 文件編譯成 .pd.go 文件
--go_out 參數(shù)
作用:指定go代碼生成的基本路徑
- protocol buffer編譯器會將生成的Go代碼輸出到命令行參數(shù)
go_out
指定的位置 go_out
標志的參數(shù)是你希望編譯器編寫 Go 輸出的目錄- 編譯器會為每個
.proto
文件輸入創(chuàng)建一個源文件 - 輸出文件的名稱是通過將
.proto
擴展名替換為.pb.go
而創(chuàng)建的
--go_opt 參數(shù)
protoc-gen-go
提供了--go_opt
參數(shù)來為其指定參數(shù),可以設(shè)置多個:
paths=import
:生成的文件會按go_package
路徑來生成,當然是在--go_out
目錄
例如,go_out/$go_package/pb_filename.pb.go
如果未指定路徑標志,這就是默認輸出模式
paths=source_relative
:輸出文件與輸入文件放在相同的目錄中
例如,一個protos/buzz.proto
輸入文件會產(chǎn)生一個位于protos/buzz.pb.go
的輸出文件。
module=$PREFIX
:輸出文件放在以 Go 包的導入路徑命名的目錄中,但是從輸出文件名中刪除了指定的目錄前綴。
例如,輸入文件 pros/buzz.proto
,其導入路徑為 example.com/project/protos/fizz
并指定example.com/project
為module
前綴,結(jié)果會產(chǎn)生一個名為 pros/fizz/buzz.pb.go
的輸出文件。
在module路徑之外生成任何 Go 包都會導致錯誤,此模式對于將生成的文件直接輸出到 Go 模塊非常有用。
--proto_path 參數(shù)
--proto_path=IMPORT_PATH
- IMPORT_PATH是 .proto 文件所在的路徑,如果忽略則默認當前目錄。
- 如果有多個目錄則可以多次調(diào)用--proto_path,它們將會順序的被訪問并執(zhí)行導入。
使用示例:
protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto // 編譯器將從 `src` 目錄中讀取輸入文件 `foo.proto` 和 `bar/baz.proto`,并將輸出文件 `foo.pb.go` 和 `bar/baz.pb.go` 寫入 `out` 目錄。如果需要,編譯器會自動創(chuàng)建嵌套的輸出子目錄,但不會創(chuàng)建輸出目錄本身
使用grpc的go插件 安裝proto-gen-go-grpc
在google.golang.org/protobuf
中,protoc-gen-go
純粹用來生成pb序列化相關(guān)的文件,不再承載gRPC代碼生成功能。生成gRPC相關(guān)代碼需要安裝grpc-go相關(guān)的插件protoc-gen-go-grpc
// 安裝protoc-gen-go-grpc go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest // 目前最新版是v1.3.0
生成grpc的go代碼:
// 主要是--go_grpc_out參數(shù)會生成go代碼 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
--go-grpc_out 參數(shù)
作用:指定grpc go代碼生成的基本路徑
命令會產(chǎn)生的go文件:
protoc-gen-go
:包含所有類型的序列化和反序列化的go代碼protoc-gen-go-grpc
:包含service中的用來給client調(diào)用的接口定義以及service中的用來給服務端實現(xiàn)的接口定義
--go-grpc_opt 參數(shù)
和protoc-gen-go
類似,protoc-gen-go-grpc
提供 --go-grpc_opt
來指定參數(shù),并可以設(shè)置多個
github.com/golang/protobuf
和 google.golang.org/protobuf
github.com/golang/protobuf
github.com/golang/protobuf
現(xiàn)在已經(jīng)廢棄- 它可以同時生成pb和gRPC相關(guān)代碼的
用法:
// 它在--go_out加了plugin關(guān)鍵字,paths參數(shù)有兩個選項,分別是 import 和 source_relative --go_out=plugins=grpc,paths=import:. *.proto
google.golang.org/protobuf
- 它
github.com/golang/protobuf
的升級版本,v1.4.0
之后github.com/golang/protobuf
僅是google.golang.org/protobuf
的包裝 - 它純粹用來生成pb序列化相關(guān)的文件,不再承載gRPC代碼生成功能,生成gRPC相關(guān)代碼需要安裝grpc-go相關(guān)的插件
protoc-gen-go-grpc
用法:
// 它額外添加了參數(shù)--go-grpc_out以調(diào)用protoc-gen-go-grpc插件生成grpc代碼 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relativ
到此這篇關(guān)于protobuf 詳解的文章就介紹到這了,更多相關(guān)protobuf 詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
從并發(fā)到并行解析Go語言中的sync.WaitGroup
Go?語言提供了許多工具和機制來實現(xiàn)并發(fā)編程,其中之一就是?sync.WaitGroup。本文就來深入討論?sync.WaitGroup,探索其工作原理和在實際應用中的使用方法吧2023-05-05使用自定義錯誤碼攔截grpc內(nèi)部狀態(tài)碼問題
這篇文章主要介紹了使用自定義錯誤碼攔截grpc內(nèi)部狀態(tài)碼問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09