0%

Protocol Buffer 简单使用

引言

最近有接触到.prof文件, 发现其为protocol buffer格式文件. 第一次接触, 这里简单的做个梳理, 如要理解细节,可参考官网.

定义

Protocol Buffers 是一种数据编码格式, 类似JSON,XML, 其不同点在于, Protoco Buffers并非完全可读的编码格式.

特点

  • 跨语言: 支持各种编程语言, C++, C#, Go, Python, Java...
  • 跨平台: win, mac, linux ...
  • 传输速度快, 数据体积小 …

常见使用场景

gRPC, Google Cloud, Envoy Proxy ...

使用流程

image-20240115151319003

  1. 定义: 首先定义一个.proto文件, 其定义了消息的数据结构;
  2. 编译: 其次通过protoc编译器, 生成对应语言例如:(Go)的数据结构源码(...pb.go);
  3. 处理: 最后通过项目, 操作pb.go实现业务功能: 序列化/反序列化...;

使用

Go语言为例

定义

定义.proto 数据格式; 具体的定义细节, 这里不做介绍, 有兴趣可参考: Proto3版本介绍.

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// [START declaration]
syntax = "proto3"; // 声明 .proto 文件版本
package tutorial; // 包定义, 防止跨项目间的命名冲突
// [END declaration]

// [START go_declaration]
option go_package = "/protobuf/go/tutorialpb"; // 生成的Go代码包结构
// [END go_declaration]

// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
}
// [END messages]

上面的.proto文件中定义了一个Person数据结构, 采用了proto3版本, 并指定代码生成在/protobuf/go/tutorialpb相对目录下.

编译

安装proto编译器

安装地址, 选择对应的平台的压缩包, 例如在Linux X86 64架构下, 选择:protoc-25.2-linux-x86_64.zip.

解压上述zip文件, 例如/usr/local下:unzip -d protoc /usr/local/protoc-25.2-linux-x86_64.zip

运行编译器

命令示例: protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

生成:

1
2
3
cd /usr/local/protoc/bin/

./protoc -I=/home/self/pkg/data/tutorial --go_out=/home/self/pkg/data/tutorial /home/self/pkg/data/tutorial/person.proto

结果:

image-20240115160243873

生成了person.pb.go数据文件;

处理

可以根据业务需求编写序列化与反序列化代码: 例如添加一个Person的需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package main

import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"

pb "self/pkg/data/tutorial/protobuf/go/pb"

"google.golang.org/protobuf/proto"
)

func promptForAddress(r io.Reader) (*pb.Person, error) {
// A protocol buffer can be created like any struct.
p := &pb.Person{}

rd := bufio.NewReader(r)
fmt.Print("Enter person ID number: ")
// An int32 field in the .proto file is represented as an int32 field
// in the generated Go struct.
if _, err := fmt.Fscanf(rd, "%d\n", &p.Id); err != nil {
return p, err
}

fmt.Print("Enter name: ")
name, err := rd.ReadString('\n')
if err != nil {
return p, err
}
// A string field in the .proto file results in a string field in Go.
// We trim the whitespace because rd.ReadString includes the trailing
// newline character in its output.
p.Name = strings.TrimSpace(name)

fmt.Print("Enter email address (blank for none): ")
email, err := rd.ReadString('\n')
if err != nil {
return p, err
}
p.Email = strings.TrimSpace(email)

return p, nil
}

// Main reads the entire person from a file, adds one person based on
// user input, then writes it back out to the same file.
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s PERSON_FILE\n", os.Args[0])
}
fname := os.Args[1]

// Read the existing address book.
in, err := ioutil.ReadFile(fname)
if err != nil {
if os.IsNotExist(err) {
fmt.Printf("%s: File not found. Creating new file.\n", fname)
} else {
log.Fatalln("Error reading file:", err)
}
}

// [START marshal_proto]
person := &pb.Person{}
// [START_EXCLUDE]
if err := proto.Unmarshal(in, person); err != nil {
log.Fatalln("Failed to parse address book:", err)
}
bs, _ := json.Marshal(person)
log.Printf("Got PERSON : %v \n", string(bs))

// Add an address.
newPerson, err := promptForAddress(os.Stdin)
if err != nil {
log.Fatalln("Error with address:", err)
}
// [END_EXCLUDE]

// Write the new person back to disk.
out, err := proto.Marshal(newPerson)
if err != nil {
log.Fatalln("Failed to encode person:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
log.Fatalln("Failed to write person:", err)
}
// [END marshal_proto]
}

执行代码: 添加一个Person数据并序列化到本地:

1
2
3
4
5
6
[root addPerson]# go run add.go ./per
./per: File not found. Creating new file.
2024/01/15 16:11:51 Got PERSON : {}
Enter person ID number: 14930
Enter name: HowardSUN
Enter email address (blank for none): 14930@qq.com

查看本地数据:

1
2
3
4
[root@ addPerson]# cat per

HowardSUN⚌t
14930@qq.com

至于为何数据编码为上述样式, 本文不做解释.

总结

Protocol Buffer是跨语言, 跨平台的数据编码格式. 其中.proto数据定义格式, 决定了各平台,各语言可以识别相同的数据格式. protocol buffer编译器支持生成各语言下的数据源码, 统一化基础操作; 最后通过业务处理, 可以实现基于序列化/反序列化的个性化操作;