前言

會有這一篇的誕生是因為原本在寫 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 檔案。

參考連結