為什么使用 protocol buffer?
我們將要使用的示例是一個非常簡單的“地址簿”應用程序喜庞,它可以在文件中讀取和寫入人們的聯(lián)系方式诀浪。
地址簿中的每個人都有一個姓名、一個 ID延都、一個電子郵件地址雷猪、一個聯(lián)系電話號碼。
你如何序列化和檢索這樣的結構化數據晰房?
有幾種方法可以解決這個問題:
使用 gobs 序列化 Go 數據結構
這在特定于 Go 的環(huán)境中是一個很好的解決方案求摇,但是如果您需要與為其他平臺編寫的應用程序共享數據,它就不能很好地工作殊者。您可以發(fā)明一種特殊方式將數據項編碼為單個字符串
例如將 4 個整數編碼為“12:3:-23:67”与境。 這是一種簡單而靈活的方法,盡管它確實需要編寫一次性的編碼和解析代碼猖吴,并且解析會產生很小的運行時成本摔刁。 這最適合編碼非常簡單的數據。將數據序列化為 XML
這種方法可能非常有吸引力海蔽,因為 XML(某種程度)是人類可讀的共屈,并且有許多語言的綁定庫。 如果您想與其他應用程序/項目共享數據党窜,這可能是一個不錯的選擇趁俊。 然而,眾所周知刑然,XML 是空間密集型的寺擂,對它進行編碼/解碼會對應用程序造成巨大的性能損失。
protocol buffer 是解決這個問題的靈活泼掠、高效怔软、自動化的解決方案。
使用 protocol buffer择镇,您可以編寫要存儲的數據結構的 .proto 描述
挡逼。
由此,protocol buffer 編譯器創(chuàng)建了一個類腻豌,該類以高效的二進制格式
實現 protocol buffer 數據的自動編碼和解析家坎。
生成的類為組成 protocol buffer 的字段提供 getter 和 setter嘱能,并將讀取和寫入 protocol buffer 的細節(jié)作為一個單元處理。
重要的是虱疏,protocol buffer格式支持隨著時間的推移擴展格式的想法惹骂,這樣代碼仍然可以讀取用舊格式編碼的數據。
示例
https://github.com/protocolbuffers/protobuf/tree/main/examples
定義proto
要創(chuàng)建地址簿應用程序做瞪,您需要從 .proto 文件開始对粪。
.proto 文件中的定義很簡單:為每個要序列化的數據結構添加一條消息,然后為消息中的每個字段指定名稱和類型装蓬。
在示例中著拭,定義消息的 .proto 文件是 addressbook.proto。
.proto 文件以包聲明開頭牍帚,這有助于防止不同項目之間的命名沖突儡遮。
syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
go_package 選項定義包的導入路徑,該路徑將包含此文件的所有生成代碼暗赶。
Go 包名稱將是導入路徑的最后一個路徑組件鄙币。
例如,示例將使用包名“tutorialpb”忆首。
option go_package = "github.com/protocolbuffers/protobuf/examples/go/tutorialpb";
接下來爱榔,定義 message。 message 是包含一組類型字段的聚合糙及。
許多標準的簡單數據類型可用作字段類型详幽,包括 bool、int32浸锨、float唇聘、double 和 string。還可以通過使用其它 message 類型作為字段類型柱搜,來為 message 添加更多結構迟郎。
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
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;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
編譯 protocol buffers
現在您已經有了一個 .proto,接下來您需要生成讀取和寫入 AddressBook(以及由此產生的 Person 和 PhoneNumber)消息所需的類聪蘸。 為此宪肖,您需要在 .proto 上運行protocol buffers 編譯器 protoc:
如果您尚未安裝編譯器,請下載軟件包并按照 README 中的說明進行操作健爬。
https://developers.google.cn/protocol-buffers/docs/downloads運行以下命令安裝 Go protocol buffers 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
編譯器插件 protoc-gen-go 將安裝在 $GOBIN 中控乾,默認為 $GOPATH/bin。 它必須在您的 $PATH 中娜遵,編譯器 protoc 才能找到它蜕衡。
- 現在運行編譯器,指定源目錄(應用程序的源代碼所在的位置设拟。如果不提供值慨仿,則使用當前目錄)久脯、目標目錄(您希望生成的代碼所在的位置;通常與 $SRC_DIR 相同)镰吆,以及 .proto 的路徑帘撰。
protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
這會在您指定的目標目錄中生成 addressbook.pb.go。
The Protocol Buffer API
生成的 addressbook.pb.go 為您提供以下有用的類型:
- AddressBook 結構體鼎姊,包含 People骡和;
- Person 結構體相赁, 包含 Name, Id, Email, Phones相寇;
- Person_PhoneNumber 結構體, 包含 Number, Type;
- The type Person_PhoneType and a value defined for each value in the Person.PhoneType enum.
p := pb.Person{
Id: 1234,
Name: "John Doe",
Email: "jdoe@example.com",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.Person_HOME},
},
}
序列化
使用 protocol buffer 的目的是序列化您的數據钮科,以便可以在其他地方對其進行解析唤衫。 在 Go 中,您使用 proto 庫的 Marshal
函數來序列化您的協(xié)議緩沖區(qū)數據绵脯。
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 函數。
調用它會將 in 中的數據解析為 protocol buffer 并將結果放入 book 中蛆挫。
// 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)
}
擴展 protocol buffer
在你發(fā)布使用你的 protocol buffer 的代碼之后赃承,遲早會想要“改進” protocol buffer 的定義。 如果您希望新buffer 向后兼容悴侵,并且您的舊 buffer 向前兼容瞧剖。那么您需要遵循一些規(guī)則。 在新版本的協(xié)議緩沖區(qū)中:
- 您不得更改任何現有字段的標簽號可免。
- 您可以刪除字段抓于。
- 您可以添加新字段,但必須使用新的標簽號(即從未在此 protocol buffer 中使用過的標簽號浇借,即使已刪除的字段也不使用)捉撮。
如果您遵循這些規(guī)則,舊代碼將愉快地閱讀新消息并忽略任何新字段妇垢。 對于舊代碼巾遭,已刪除的單個字段將僅具有其默認值,而刪除的重復字段將為空闯估。 新代碼也將透明地讀取舊消息灼舍。
但是,請記住睬愤,舊消息中不會出現新字段片仿,因此您需要對默認值做一些合理的事情。 使用特定類型的默認值:對于字符串尤辱,默認值為空字符串砂豌。 對于布爾值厢岂,默認值為 false。 對于數字類型阳距,默認值為零塔粒。