文章目錄
- 一、介紹
- 二、安裝
- 三、protoc3語法
- 1、 protoc3 與 protoc2區別
- 2、proto3生成go代碼
- 包
- Message
- 內嵌Message
- 字段
- 單一標量字段
- 單一message字段
- 可重復字段slice
- map字段
- 枚舉
一、介紹
Protobuf
是Google
旗下的一款平臺無關,語言無關,可擴展的序列化結構數據格式。所以很適合用做數據存儲和作為不同應用,不同語言之間相互通信的數據交換格式,只要實現相同的協議格式,即同一proto
文件被編譯成不同的語言版本,加入到各自的工程中去,這樣不同語言就可以解析其他語言通過Protobuf
序列化的數據。目前官網提供了C++,Python,JAVA,GO
等語言的支持。
二、安裝
- Mac上安裝Protoc3
- Windows安裝Protoc3
- 安裝
protoc-gen-go
protoc-gen-go
是生成Go
代碼的protocolbuffers
編譯器。可以理解為一個編譯器插件,配合protoc
來使用。在命令行執行如下命令即可完成安裝:
go get -u github.com/golang/protobuf/protoc-gen-go@latest
網上資料推薦的基本都是這個命令,但目前該模塊已被棄用,繼續使用該命令將出現錯誤,提示該庫已經被棄用,讓我們使用go get -u google.golang.org/protobuf/
三、protoc3語法
1、 protoc3 與 protoc2區別
proto3
在proto2
的基礎上去掉了一些復雜的語法和特性,更強調約定而弱化語法。主要幾點區別如下:
proto
文件第一行非空白非注釋行,必須指定版本,syntax = "proto3"
;如果不指定,則默認是proto2
- 字段規則移除了
required
,并把optional
改名為singular
,省略不寫時,默認就是singular
。 repeated
字段默認采用packed
編碼,在proto2
中,需要明確使用[packed=true]
來為字段指定比較緊湊的packed
編碼方式。- 語言增加
Go、Ruby、JavaNano
支持,即在proto2
時并不支持go
語言。 - 移除了
default
選項,在proto2
中,可以使用default
選項為某一字段指定默認值。在proto3
中,字段的默認值只能根據字段類型由系統決定。也就是說,默認值全部是約定好的,而不再提供指定默認值的語法。在字段被設置為默認值的時候,該字段不會被序列化。這樣可以節省空間,提高效率。
但這樣就無法區分某字段是根本沒賦值,還是賦值了默認值。 所以一般需要避免將默認值作為任何行為的觸發方式。例如
enum AudienceDisplayTypeEnum {NoValue = 0; // (占位符)說明端上沒有傳入此參數,請勿使用CurrentCount = 1; // 展示當前直播間內人數AccumulativeCount = 2; // 展示直播間累計人數SettingEntranceClosed = 99; // 端上拿到則不展示此選項,相當于配置項是否出現的開關
}
此例子是控制直播間展示在線人數還是看播人次的開關,開關僅兩個取值:true
和false
,但這里并沒有使用bool
類型,因為bool
型默認值是false
,即使前端沒有給我們傳該值,我們也會拿到false
值,從而可能當成是前端傳過來的值,切換開關,因此使用枚舉。使用枚舉后,不使用0
和1
表示開關的打開與關閉,因為0
是枚舉的默認值,也不應該作為控制行為的值,因此有業務含義的從序號為1
的字段開始。
6. 枚舉類型的第一個字段必須為 0
,因為枚舉會把第一個字段作為默認值
7. 增加了JSON
映射特性,如
message XXXRequest {string name = 1; int64 begin_time = 2 (go.tag = "json:\"beginTime\"");int64 end_time = 3;int32 page_no = 4;int32 page_size = 5;
}
2、proto3生成go代碼
Go Proto Buffer代碼生成官網文檔地址
包
如果一個.proto
文件中有包聲明,生成的源代碼將會使用它來作為Go
的包名,如果.proto
的包名中有.
,在Go
包名中會將.
轉換為_
。舉例來說proto
包名example.high_score
將會生成Go
包名example_high_score
。
在.proto
文件中可以使用option go_package
指令來覆蓋上面默認生成Go
包名的規則。比如說包含如下指令的一個.proto
文件
package example.high_score;
option go_package = "/test";
生成的Go
源代碼的包名是test
。
如果一個.proto
文件中不包含package
聲明,生成的源代碼將會使用.proto
文件的文件名作為Go
包名,.
會被首先轉換為_
。舉例來說一個名為high.score.proto
不包含package
聲明的文件將會生成文件high.score.pb.go
,他的Go包名是high_score
。
Message
一個簡單的message
聲明:
message Foo {}
protocol buffer
編譯器將會生成一個名為Foo
的結構體,var A *Foo
為實現了proto.Message
接口的Foo
類型的指針,因為Foo
實現了proto.Message
接口中ProtoMessage()
方法,生成的XXX.pb.go
文件將包含如下代碼片段,注意看注釋哦
type Foo struct {
}// 重置proto為默認值
func (m *Foo) Reset() { *m = Foo{} }// String 返回proto的字符串表示
func (m *Foo) String() string { return proto.CompactTextString(m) }// ProtoMessage作為一個tag 確保其他人不會意外的實現
// proto.Message 接口.
func (*Foo) ProtoMessage() {}
內嵌Message
一個message
可以聲明在其他message
的內部。比如:
message Foo {message Bar {}
}
這種情況,編譯器會生成兩個結構體:Foo
和Foo_Bar
。
字段
編譯器會為每個在message
中定義的字段生成一個Go
結構體的字段,字段的確切性質取決于它的類型以及它是singular,repeated,map
還是oneof
字段。
注意生成的Go
結構體的字段將始終使用駝峰命名,即在.proto
文件中消息字段用的是小寫加下劃線(工作中基本都是這種形式),生成的Go
代碼會是大駝峰命名。大小寫轉換的原理如下:
- 首字母會大寫,如果
message
中字段的第一個字符是_
,它將被替換為X
。 - 如果內部下劃線后跟小寫字母,則刪除下劃線,并將后面跟隨的字母大寫。
因此,proto
字段foo_bar_baz
在Go
中變成FooBarBaz
,_my_field_name
變為XMyFieldName
。
單一標量字段
對于包級別字段定義:
int32 id = 1;
編譯器將生成一個帶有名為Id
的int32
字段和一個訪問器方法GetId()
的結構,該方法返回結構體中Id
字段的零值(如果字段未設置(數值型零值為0
,字符串為空字符串))。
單一message字段
給出如下消息類型
message Bar {}
對于一個有Bar
類型字段的消息:
// proto3
message Baz {Bar foo = 1;
}
編譯器將會生成一個Go
結構體
type Baz struct {Foo *Bar
}
消息類型的字段可以設置為nil
,這意味著該字段未設置。
編譯器還生成一個func(m *Baz)GetFoo() *Bar
輔助函數。這讓不在中間檢查nil
值進行鏈式調用成為可能,因為該方法中會進行相關字段的nil
判斷。
可重復字段slice
每個重復的字段在Go
中的結構中生成一個T
類型的slice
,其中T
是字段的元素類型。對于帶有重復字段的消息:
message Baz {repeated Bar foo = 1;
}
編譯器會生成如下結構體:
type Baz struct {Foo []*Bar
}
同樣,對于字段定義repeated bytes foo = 1;
編譯器將會生成一個帶有類型為[][]byte
, 名為Foo
的字段的Go
結構體。對于可重復的枚舉repeated MyEnum bar = 2;
,編譯器會生成帶有類型為[]MyEnum
, 名為Bar
的字段的Go
結構體。
map字段
每個映射字段會在Go
的結構體中生成一個map[TKey]TValue
類型的字段,其中TKey
是字段的鍵類型,TValue
是字段的值類型。對于下面這個消息定義:
message Bar {}message Baz {map<string, Bar> foo = 1;
}
編譯器生成Go
結構體
type Baz struct {Foo map[string]*Bar
}
枚舉
給出如下枚舉
message SearchRequest {enum Corpus {UNIVERSAL = 0;WEB = 1;IMAGES = 2;LOCAL = 3;NEWS = 4;PRODUCTS = 5;VIDEO = 6;}Corpus corpus = 1;
}
編譯器將會生成一個枚舉類型和一系列該類型的常量。
對于消息中的枚舉(像上面那樣),類型名字以消息名開頭
type SearchRequest_Corpus int32
對于包級別的枚舉:
// .proto
enum Foo {DEFAULT_BAR = 0;BAR_BELLS = 1;BAR_B_CUE = 2;
}
Go
中的類型不會對proto
中的枚舉名稱進行修改:
type Foo int32
此類型具有String()
方法,該方法返回給定值的名稱。
Enum()
方法使用給定值初始化新分配的內存并返回相應的指針:
func (Foo) Enum() *Foo
編譯器為枚舉中的每個值生成一個常量。對于消息中的枚舉,常量以消息的名稱開頭:
const (SearchRequest_UNIVERSAL SearchRequest_Corpus = 0SearchRequest_WEB SearchRequest_Corpus = 1SearchRequest_IMAGES SearchRequest_Corpus = 2SearchRequest_LOCAL SearchRequest_Corpus = 3SearchRequest_NEWS SearchRequest_Corpus = 4SearchRequest_PRODUCTS SearchRequest_Corpus = 5SearchRequest_VIDEO SearchRequest_Corpus = 6
)
對于包級別的枚舉,常量以枚舉名稱開頭:
const (Foo_DEFAULT_BAR Foo = 0Foo_BAR_BELLS Foo = 1Foo_BAR_B_CUE Foo = 2
)
protobuf
編譯器還生成從整數值到字符串名稱的映射以及從名稱到值的映射:
var Foo_name = map[int32]string{0: "DEFAULT_BAR",1: "BAR_BELLS",2: "BAR_B_CUE",
}
var Foo_value = map[string]int32{"DEFAULT_BAR": 0,"BAR_BELLS": 1,"BAR_B_CUE": 2,
}