個人覺得,有谷歌翻譯魄健,百度翻譯。加上自己的理解舰涌。自個看看官方文檔也還不錯秘蛔。
下面98%是谷歌翻譯跟百度翻譯的。剩余2%是我自己按照自己的理解岁忘。
免責聲明:粗糙翻譯辛慰,隨便看看哈。想糾正的干像。留言即可帅腌。本人水平有限。
Protocol Buffer Basics: Go
通過創(chuàng)建一個簡單的示例應用程序麻汰,它向您展示如何:
- 在.proto文件中定義消息格式速客。
- 使用協(xié)議緩沖區(qū)編譯器。
- 使用Go協(xié)議緩沖區(qū)API來編寫和讀取消息五鲫。
為何使用protobuf
假設我們的示例是一個非常簡單的“地址簿”應用程序溺职,可以在文件中讀取和寫入人員的聯(lián)系人詳細信息。地址簿中的每個人都有姓名位喂,ID浪耘,電子郵件地址和聯(lián)系電話號碼。
那么序列化和恢復這樣結構(序列化與反序列化)的幾種方法:
使用gobs序列化Go數(shù)據(jù)結構塑崖。這是Go特定環(huán)境中的一個很好的解決方案七冲,但如果您需要與為其他平臺編寫的應用程序共享數(shù)據(jù),它將無法正常工作规婆。
您可以發(fā)明一種特殊的方法將數(shù)據(jù)項編碼為單個字符串 - 例如將4個整數(shù)編碼為“12:3:-23:67”澜躺。這是一種簡單而靈活的方法蝉稳,雖然它確實需要編寫一次性編碼和解析代碼,并且解析會產生較小的運行時成本掘鄙。這最適合編碼非常簡單的數(shù)據(jù)耘戚。
將數(shù)據(jù)序列化為XML。這種方法非常有吸引力操漠,因為XML(有點)是人類可讀的收津,并且有許多語言的綁定庫。如果您想與其他應用程序/項目共享數(shù)據(jù)颅夺,這可能是一個不錯的選擇朋截。然而,XML是眾所周知的空間密集型吧黄,并且編碼/解碼它會對應用程序造成巨大的性能損失部服。此外,導航XML DOM樹比通常在類中導航簡單字段要復雜得多拗慨。
協(xié)議緩沖區(qū)是一種靈活廓八、高效、自動化的解決方案赵抢,可以精確地解決這個問題剧蹂。使用協(xié)議緩沖區(qū),可以編寫要存儲的數(shù)據(jù)結構的.proto描述烦却。由此宠叼,協(xié)議緩沖區(qū)編譯器創(chuàng)建了一個類,該類用有效的二進制格式實現(xiàn)協(xié)議緩沖區(qū)數(shù)據(jù)的自動編碼和解析其爵。生成的類為組成協(xié)議緩沖區(qū)的字段提供getter和setter冒冬,并負責將協(xié)議緩沖區(qū)作為一個單元讀寫的詳細信息。重要的是摩渺,協(xié)議緩沖區(qū)格式支持隨著時間的推移擴展格式的思想简烤,這樣代碼仍然可以讀取用舊格式編碼的數(shù)據(jù)。
示例代碼https://github.com/protocolbuffers/protobuf/tree/master/examples
定義你的protocol 格式
要創(chuàng)建通訊簿應用程序摇幻,您需要從.proto文件開始横侦。.proto文件中的定義很簡單:為要序列化的每個數(shù)據(jù)結構添加消息,然后為消息中的每個字段指定名稱和類型绰姻。在我們的示例中枉侧,定義消息的.proto文件是addressbook.proto。
.proto文件以包聲明開頭狂芋,這有助于防止不同項目之間的命名沖突棵逊。
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto"
在go中,包名就是作為Go的包名银酗。除非你指定了一個go_package(編譯時可以指定go_package)辆影。你要是提供了go_package ,你定義的包名要避免與非Go語言的Protobuf 的命名空間的沖突黍特。
vscode 可以按照proto3的插件蛙讥,這樣編輯起來比較方便。
下面灭衷,你有了你的消息定義次慢。一個消息是包含一組類型字段的聚合。許多標準的簡單數(shù)據(jù)類型都可以作為字段類型翔曲,包括bool迫像,int32, float瞳遍,double闻妓,和string。您還可以使用其他消息類型作為字段類型掠械,為郵件添加更多結構由缆。
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type =2;
}
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
}
message AddressBook {
repeated Person people = 1;
}
在上面的示例中,Person消息包含 PhoneNumber消息猾蒂,而AddressBook消息包含Person消息均唉。您甚至可以定義嵌套在其他消息中的消息類型 -?? 如您所見, PhoneNumber類型在內部定義Person肚菠。您還可以定義enum舔箭,如果你希望你的領域之一,有預定義的值列表中的一個類型-在這里你要指定一個電話號碼可以是一個MOBILE蚊逢,HOME或 WORK
每個元素上的“= 1”层扶,“= 2”標記標識該字段在二進制編碼中使用的唯一“標記”。
如果未設置字段值时捌, 則使用默認值:數(shù)字類型為零怒医,字符串為空字符串,bools為false奢讨。對于嵌入式消息稚叹,默認值始終是消息的“默認實例”或“原型”,其中沒有設置其字段拿诸。調用訪問器以獲取尚未顯式設置的字段的值始終返回該字段的默認值扒袖。
如果是字段repeated
,則字段可以重復任意次數(shù)(包括零)亩码。重復值的順序將保留在協(xié)議緩沖區(qū)中季率。將重復字段視為動態(tài)大小的數(shù)組。
編譯protobuf
下載protoc(windows描沟。下載,把protoc的bin目錄放到環(huán)境變量里飒泻。mac可以使用brew install 去安裝)鞭光。
生成go所需要的pb.go文件需要 go get -u github.com/golang/protobuf/protoc-gen-go。
現(xiàn)在運行編譯器泞遗,指定源目錄(應用程序的源代碼所在的位置 - 如果不提??供值惰许,則使用當前目錄),目標目錄(您希望生成的代碼在哪里;通常同$SRC_DIR) 史辙,以指定你要編譯的.proto汹买。
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
輸出go用的文件用go_out,其他語言有其他語言的選項聊倔。
protobuf API
我在我工程的目錄下抄寫了官網(wǎng)代碼晦毙,并在當前目錄下執(zhí)行protoc addressbook.proto --go_out=./
生成的addressbook.pb.go為你提供了下面可用的類型:
AddreBook包含了People
Person里面有Name,Id,Email,Phones.
Person_PhoneNumber包含Number 和Type
類型 Person_PhoneType和Person.PhoneType枚舉
去看下官方示例。
如何創(chuàng)建Person實例
p := pb.Person{
Id: 1234,
Name: "John Doe",
Email: "jdoe@example.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_HOME},
},
}
寫一個消息
使用協(xié)議緩沖區(qū)的全部目的是序列化您的數(shù)據(jù)耙蔑,以便可以在其他地方解析它见妒。在Go中,您使用proto
庫的Marshal 函數(shù)來序列化協(xié)議緩沖區(qū)數(shù)據(jù)纵潦。指向協(xié)議緩沖區(qū)消息的指針struct
實現(xiàn)proto.Message
接口徐鹤。調用proto.Marshal
返回以其有線格式編碼的protobuf。例如邀层,我們在add_person
命令中使用此函數(shù) :
book := &pb.AddressBook{}
// ...
// Write the new address book back to disk.
out, err := proto.Marshal(book)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
log.Fatalln("Failed to write address book:", err)
}
讀消息
要解析編碼消息返敬,請使用proto
庫的 Unmarshal 函數(shù)。調用此方法將數(shù)據(jù)解析buf
為協(xié)議緩沖區(qū)并將結果放入pb
寥院。因此劲赠,要在list_people
命令中解析文件 ,我們使用:
// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
log.Fatalln("Failed to parse address book:", err)
}
擴展protobuf
當你代碼上線之后秸谢,避免不了修改proto文件的定義凛澎。你需要注意一些兼容性規(guī)則。在你的新版本代碼中:
- 不能更改現(xiàn)有字段的編號估蹄。
- 可以刪除字段
- 可以添加字段塑煎,新增字段使用新的編號(從沒用過的,哪怕是給刪除了字段使用過的)
遵循了這些規(guī)則臭蚁,舊代碼在讀消息的時候會忽略你的新代碼最铁。對于刪除的字段,舊代碼也會給其默認值垮兑,刪除的repeated字段將為空冷尉。新代碼也
舊消息中不會出現(xiàn)新字段,因此需要使用默認值執(zhí)行合理操作系枪。使用特定于類型的 默認值 :對于字符串雀哨,默認值為空字符串。對于布爾值,默認值為false雾棺。對于數(shù)字類型膊夹,默認值為零。
示例代碼跑一跑
官方的代碼示例垢村。https://github.com/protocolbuffers/protobuf
首先割疾,我go get下來了。然后用vscode 打開了這個項目嘉栓。
在proto文件目錄下我創(chuàng)建了一個文件夾tutorial
(當然文檔里面有個Makefile會幫你做這件事情,但我windows不好使拓诸,手動來)侵佃。以便于生成pb.go文件放入其中(不然 list_people_test.go 里面引入的pb找不到)。
第一例子是以下是list_people
命令的單元測試示例 奠支,說明如何創(chuàng)建Person實例:
go test -v list_people_test.go list_people.go
(-v是看命令都干了些啥)
寫一條消息(Writing a Message)
go run add_person.go addressbook.data
然后就輸入數(shù)據(jù)寫入到指定的文件中馋辈。
Enter person ID number: 123434
Enter name: mike
Enter email address (blank for none): mike@gmail.com
Enter a phone number (or leave blank to finish): 178933
Is this a mobile, home, or work phone? home
Enter a phone number (or leave blank to finish): 98783
Is this a mobile, home, or work phone? work
Enter a phone number (or leave blank to finish): 123333
Is this a mobile, home, or work phone? mobile
打開addressbook.data,看看內容
"
?mike?{??mike@gmail.com"
?178933
;
?mike??????mike@gmail.com"
?178933??"
?98783??"
?123333
讀一條消息(Reading a Message)
go run list_people.go addressbook.data
Person ID: 123
Name: mike
E-mail address: mike@gmail.com
Mobile phone #: 178933
Person ID: 123434
Name: mike
E-mail address: mike@gmail.com
Home phone #: 178933
Work phone #: 98783
我的思考
剝去了絲絲神秘倍谜?就像我當年剛入行讓我給移動端寫接口迈螟,我一頭霧水什么是接口?前輩說ajax寫過吧尔崔。寫過……_
json序列化反序列化知道吧答毫。json改成proto。傳的參數(shù)(類型是Message)是proto文件生成的pb.go文件中的Message季春。也可以看看Message類型是什么洗搂?
type Message interface {
Reset()
String() string
ProtoMessage()
}
只要實現(xiàn)了這三個接口的,就是Message類型了(想想Duck Type)载弄。
我在我工程下耘拇,簡單的練習了下。
p := pb.Person{
Id: 1234,
Name: "Mike",
Email: "mike@gmail.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_HOME},
},
}
book := &pb.AddressBook{
People: []*pb.Person{&p},
}
out, err := proto.Marshal(book)
if err != nil {
log.Fatalln("Failed to encode address book:", err)
}
fmt.Println(out)
book1 := &pb.AddressBook{}
proto.Unmarshal(out, book1)
fmt.Printf("%s\n", string(out))
fmt.Printf("%v", book1)
輸出
[10 39 10 4 77 105 107 101 16 210 9 26 14 109 105 107 101 64 103 109 97 105 108 46 99 111 109 34 12 10 8 53 53 53 45 52 51 50 49 16 1]
'
?Mike?? ??mike@gmail.com"
555-4321??
people:<name:"Mike" id:1234 email:"mike@gmail.com" phones:<number:"555-4321" type:HOME > >
參考資料:
《go test 測試用例那些事》https://www.cnblogs.com/li-peng/p/10036468.html