在 Go 中與 Protobuf 共舞 ?!
前言
會有這一篇的誕生是因為原本在寫 gRPC 的筆記時發現篇幅太長,想說還是拆開寫好了。於是乎這一篇就出現了。
主要內容
本篇我們會先對 Protocol Buffer 的語法進行瞭解,接著撰寫、並轉譯一個範例,最後實際的跑一個範程式。
Protocol Buffers(簡稱:ProtoBuf)是一種開源跨平台的序列化資料結構的協定。其對於儲存資料或在網路上進行通訊的程式是很有用的。這個方法包含一個介面描述語言,描述一些資料結構,並提供程式工具根據這些描述產生程式碼,這些代碼將用來生成或解析代表這些資料結構的位元組流。 資料來源: https://zh.wikipedia.org/zh-tw/Protocol_Buffers
資料型態
int / int32 / int64
uint / uint32 / uint64
bool
float
double
bytes
string
enum ( Enumeration )
first defined enum value, which must be 0.
map
any
timestamp / duration
可以參考這裡
資料欄修飾字
- oneof
- required 表示該資料欄是必要的。
- optional 指該資料欄是可選的 (0筆或1筆)。
- repeated 指該資料欄是可以有多筆的。
- reserved
範例
1syntax = "proto3";
2
3option go_package = "foo.example.idv/protobuf/example";
4
5import "google/protobuf/timestamp.proto";
6
7message Employee {
8 string name = 1;
9 google.protobuf.Timestamp Birthday = 2;
10};
11
12message Company {
13 string name = 1;
14 repeated Employee employees = 2;
15};
安裝 Protocol Buffer 編譯器
1brew install protobuf
2go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
產生相應的 Go 程式碼
1protoc --go_out=. \
2 --go_opt=paths=source_relative \
3 protobuf/example/foo.proto
如果出現
protoc-gen-go: program not found or is not executable
這個錯誤,請同學先確認一下 ~/go/bin
是否有在你的 $PATH 中。
如果沒有就把它加進去吧!
你可以
export PATH=$HOME/go/bin:$PATH
結果
1ls
2foo.pb.go foo.proto
實際用看看
我們來撰寫一支程式,實際的使用剛剛產生的程式碼。它會建立一個使用 protobuf 結構的序列化檔案 foo。並且再將它讀回並顯示出來。
1
2 com := &example.Company{
3 Name: "FooBarBar",
4 Employees: []*example.Employee{
5 {Name:"Alice"},
6 {Name:"Bonny"},
7 {Name:"Candy"},
8 },
9 }
10
11 out, err := proto.Marshal(com)
12 if err != nil {
13 log.Fatalln("Failed to encode address book:", err)
14 }
15 if err := ioutil.WriteFile("foo", out, 0644); err != nil {
16 log.Fatalln("Failed to write company:", err)
17 }
18
19 in, err := ioutil.ReadFile("foo")
20 if err != nil {
21 log.Fatalln("Error reading file:", err)
22 }
23
24 com2 := &example.Company {}
25 if err := proto.Unmarshal(in, com2); err != nil {
26 log.Fatalln("Failed to parse company:", err)
27 }
實際來一遍
1mkdir -p test/protobuf/example
2cd test
3cat << EOF > protobuf/example/foo.proto
4syntax = "proto3";
5option go_package = "foo.example.idv/protobuf/example";
6
7import "google/protobuf/timestamp.proto";
8
9message Employee {
10 string name = 1;
11 google.protobuf.Timestamp Birthday = 2;
12};
13
14message Company {
15 string name = 1;
16 repeated Employee employees = 2;
17};
18EOF
19
20cat << EOF > main.go
21package main
22
23import (
24 "fmt"
25 "io/ioutil"
26 "log"
27
28 "foo.example.idv/protobuf/example"
29 "google.golang.org/protobuf/proto"
30)
31
32func main(){
33 com := &example.Company{
34 Name: "FooBarBar",
35 Employees: []*example.Employee{
36 {Name:"Alice"},
37 {Name:"Bonny"},
38 {Name:"Candy"},
39 },
40 }
41 fmt.Println("original")
42 fmt.Println(com)
43 fmt.Println("==============================")
44 out, err := proto.Marshal(com)
45 if err != nil {
46 log.Fatalln("Failed to encode address book:", err)
47 }
48 if err := ioutil.WriteFile("foo", out, 0644); err != nil {
49 log.Fatalln("Failed to write company:", err)
50 }
51
52 in, err := ioutil.ReadFile("foo")
53 if err != nil {
54 log.Fatalln("Error reading file:", err)
55 }
56 com2 := &example.Company {}
57 if err := proto.Unmarshal(in, com2); err != nil {
58 log.Fatalln("Failed to parse company:", err)
59 }
60 fmt.Println("read from file")
61 fmt.Println(com2)
62}
63EOF
64go mod init foo.example.idv
65go get google.golang.org/protobuf/proto
66protoc --go_out=. --go_opt=paths=source_relative protobuf/example/foo.proto
67go mod tidy
68clear
69go run main.go
結果
1original
2name:"FooBarBar" employees:{name:"Alice"} employees:{name:"Bonny"} employees:{name:"Candy"}
3==============================
4read from file
5name:"FooBarBar" employees:{name:"Alice"} employees:{name:"Bonny"} employees:{name:"Candy"}
小結
本文的介紹十分的簡約,如果想要瞭解更多可以參考官方網站的說明。
如果對 protobuf
實際的資料結構有興趣不仿可以對著官方文件研究一下 實際來一遍
所產生的 foo
檔案。