Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

My Blog

从此烟雨落金城,一人撑伞两人行。

Protocol Buffers 概述

简介

Protocol Buffers语言无关,平台无关,可扩展的结构化数据序列化方案, 用于协议通讯, 数据存储和其他更多用途。是一个灵活,高效,自动化的结构化数据序列化机制。

原理

在.proto文件中定义protocol buffer消息类型来指定要序列化的信息如何组织。

protocol buffer信息是一个小的信息逻辑记录,包含一序列的”名字-值”对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}

repeated PhoneNumber phone = 4;
}

每个消息类型有一个或者多个唯一的编号的字段,而每个字段有一个名字和值类型。

在.proto文件上运行对应应用语言的protcol buffer的编译器来生成数据访问类。

这些类为每个字段(类似name()或者set_name())提供简单的访问器,还有用于序列化/解析整个结构到/从原始字节的方法。

安装使用

安装

下载通用编译器

地址:https://github.com/protocolbuffers/protobuf/releases

Protobuf 运行时安装

grpc官网安装
1
2
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

使用

  1. 编写.proto文件
  2. 使用protoc工具生成代码文件
  3. 调用
1
2
3
4
5
6
7
8
9
10
11
syntax = "proto3";
// 指定版本
option go_package = "./model";
// 指定生成的文件存放位置
package model;
// 指定包名
message User{
string name = 1;
int32 age = 32;
}

1
protoc --go_out=. --go-grpc_out=. helloworld/helloworld.proto
  • –go_out .pd.go文件生成目录
  • –go-grpc_out _grpc.pd.go文件生成目录
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
package main

import (
"fmt"
"google.golang.org/protobuf/proto"
"protobuftest/model"
)

func main() {
user := &model.User{
Name: "twz",
Age: 32,
}
marshal, err := proto.Marshal(user)
if err != nil {
return
}
newUser := &model.User{}

err = proto.Unmarshal(marshal, newUser)
if err != nil {
return
}
fmt.Print(newUser)

}

定义消息类型

定义消息类型.proto文件

1
2
3
4
5
6
7
8
syntax = "proto3";
// 使用的是proto3语法
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
// SearchRequest消息定义指定了三个字段

为消息定义中的每个字段指定一个介于1和#之间的数字,给定的数字在该消息的所有字段中必须是唯一的。

字段规则

  • required:消息体中必填字段,不设置会导致编解码异常。默认使用。
  • optional: 消息体中可选字段。生成指针类型。
  • repeated: 消息体中可重复字段,重复的值的顺序会被保留(例如位置3)在go中重复的会被定义为切片。
1
2
3
4
5
6
message User {
string username = 1;
int32 age = 2;
optional string password = 3;
repeated string address = 4;
}

定义多种消息类型

1
2
3
4
5
6
7
8
9
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}

message SearchResponse {
...
}

添加注释

1
2
3
4
5
6
7
8
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */

message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 results_per_page = 3; // Number of results to return per page.
}

对于Go,编译器生成一个.pb.go文件,每个文件都有一个类型消息类型。

值类型

.proto Type GO Type
double float64
float float32
int32 Int32
uint32 uint32
int64 long
bool bool
string string
bytes []byte

默认值

被编码的消息没有包含特定的简单元素, 被解析的对象对应的字段被设置为默认值。

  • 对于strings, 默认值是空字符串(注, 是””, 而不是null)
  • 对于bytes, 默认值是空字节(注, 应该是byte[0], 注意这里也不是null)
  • 对于boolean, 默认值是false.
  • 对于数字类型, 默认值是0.
  • 对于枚举, 默认值是第一个定义的枚举值, 而这个值必须是0.
  • 对于消息字段, 默认值是null.

枚举

能希望某个字段只能有预先定义的多个值中的一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}

Corpus 枚举的第一个常量设置到0: 每个枚举定义必须包含一个映射到0的常量作为它的第一个元素.

  • 必须有一个0值, 这样我们才能用0来作为数值默认值.
  • 0值必须是第一个元素

消息调用

可以使用其他消息类型作为字段类型。

1
2
3
4
5
6
7
8
9
message SearchResponse {
repeated Result result = 1;
}

message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}

导入定义

消息类型已经在其他的.proto文件中定义。

导入来使用来自其他.proto文件的定义. 为了导入其他.proto的定义, 需要在文件的顶端增加导入声明:

1
import "myproject/other_protos.proto";

需要移动.proto文件到新的位置。

在原有位置放置一个伪装的.proto文件, 通过使用import public方式转发所有的import到新的位置。其他任何导入这个包含import public语句的proto文件都可以透明的得到通过import public方法导入的依赖。

protocol编译器在通过命令行-I/–proto_path参数指定的目录集合中搜索导入的文件。如果没有指定, 则在编译器被调用的目录下查找. 通常应该设置–proto_path参数到项目所在的根目录然后为所有的导入使用完整限定名。

消息嵌套

可以在消息类型内部定义和使用消息类型。

1
2
3
4
5
6
7
8
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result result = 1;
}

在父消息类型之外重用消息类型。

1
2
3
message SomeOtherMessage {
SearchResponse.Result result = 1;
}

定义服务

在.proto文件中定义RPC服务接口, 然后protocol buffer编译器会生成所选语言的服务接口代码和桩(stubs)。

定义一个RPC服务,带一个方法处理SearchRequest并返回SearchResponse。

1
2
3
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}

生成类

  1. 定义在.proto文件中的消息类型
  2. 在.proto文件上运行protocol buffer编译器protoc
  3. 对于Go, 需要为编译器安装特别的代码生成插件

选项参数

  • –proto_path=:指定一个目录用于查找.proto文件, 当解析导入命令时. 缺省使用当前目录。
  • –go_out=:生成代码文件的目录。
1
protoc --proto_path=IMPORT_PATH  --go_out=DST_DIR

评论