gRPC 学习笔记

最近因为在开一个新坑,涉及到插件与主程序之间的通信问题。在 Go 官方的 RPC 包和🕊家出的 gRPC 之间摇摆了下后,最终决定选择了 gRPC。但是因为 gRPC 在 2020 年进行了修订,因此网络上的大多数基于旧版本的教程已经用不了了,于是在这里记录一下我的学习过程。

本文主要参照 gRPC 的官方文档和网络资料,所有用到的资料都将在文中标记出处。

gRPC 的安装

安装编译器

下载软件包并按照 README 中的说明进行操作。[1]

↑官方文档是这样说的↑

实际上 emmmm 就用./bin/protoc.exe就好了。你们如果想用的时候方便一些可以把它加到系统变量里面。

安装 Go protocol buffers 插件

1
2
go get google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go

编译器插件protoc-gen-go将安装在 $GOBIN,默认为$GOPATH/bin。编译器路径必须添加到您的$PATH中,执行protoc时才能找到它。[1:1]执行成功似乎不会有什么输出,所以只要顺利结束就好了。

gRPC 入门

走教程

首先,随便写一个hello.proto,定义一个HelloService接口[2]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax = "proto3";

package main;

option go_package = ".;main";

//定义消息"String"
message String {
//[类型] [名称] = [编号]
string value = 1;
}

//定义一个名为"HelloService"的服务
service HelloService {
rpc Hello (String) returns (String);
}

然后编译成 go 的格式

1
protoc --proto_path=_IMPORT_PATH_ --go_out=_DST_DIR_ --go_opt=paths=source_relative:.
  • --proto_path_IMPORT_PATH_表示proto文件的搜索路径。如果省略,则使用当前目录。还可以通过--proto_path多次传递选项来指定多个导入目录。将按顺序搜索它们。--proto_path也可以简写成-I=_IMPORT_PATH_[3]
  • --go_out:指定 go 文件输出的路径_DST_DIR_。该路径必须已经存在,否则无法正常运行。但该路径下的其他目录如有需要则会被自动创建。[4]
  • --go_opt=paths=source_relative:让加了 option go_package 声明的 proto 文件可以将 go 代码编译到与其同目录,否则会输出到option go_package中声明的包目录下。[5][6]
  • --go-grpc_opt=paths=source_relative:同上,不过这个生成的是 grpc 服务文件。
  • --go-grpc_opt=requireUnimplementedServers=false:禁用 requireUnimplementedServers。默认为启用状态,它的具体用法可以参考这里的内容

比如我这样:

1
protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --go-grpc_opt=requireUnimplementedServers=false hello.proto

就会生成hello.pb.gohello_grpc.pb.go文件。

Protocol Buffers 语法学习

Protocol Buffers 目前有 2 和 3 两个版本。因为我主要学习 Protocol Buffers 3,因此下面的内容若无特别注明均为 Protocol Buffers 3 的。

Protocol Buffers 使用 .proto 文件来保存文档。在上面的hello.proto中已经写出了基本的.proto文件的结构,下面将详细说明各个部分的写法。

文件的头部

syntax

.proto文件的第一个非注释行,用于说明本文件使用的 Protocol Buffers 版本,不填写的话默认为 Protocol Buffers 2。

1
2
3
4
5
//使用 Protocol Buffers 3
syntax = "proto3";

//使用 Protocol Buffers 2
syntax = "proto2";

package

下面的一行为这个 proto 文件的包名,在同一文件夹下 proto 文件要声明为同一个package。**而且同一个包内的不同 proto 文件之间的引用也需要用import来导入。**不过这个package仅作用于 proto 文件,和生成的 go 代码无关。(但实际上如果文件没有定义option go_package的话,编译器会根据package来猜测要生成的 go 文件的包名)

1
2
//声明包名为main
package main;

option go_package

option go_package用于声明生成的 go 文件所属包的完整包名,例如github.com/wsndshx/MasterService/XXX啥的,如果是 main 包的话则使用.;main即可。

1
option go_package = ".;main";

有关于packagego_package对生成的 go 文件的影响的详细内容,可以去看看这篇文章:

Message 类型

message消息对应的是 golang 中的struct结构体,里面的元素类型除了支持常规的例如int32 string bool类型外,还支持repeated(数组) enum(枚举) message(消息) map(表)。对于 ProtoBuf 支持的类型及其在其他语言中对应的类型列表可以在这里找到:Language Guide | Protocol Buffers | Google Developers

对于一个常规的message,可以这样去定义

1
2
3
4
5
6
message Latest{
uint32 room = 1;
float used = 2;
float remaining = 3;
google.protobuf.Timestamp date = 4;
}

写法和定义一个struct差不多,只不过在每个元素的后面要加上消息编号,一般从1开始排列。

对于要频繁使用的元素,最好将其消息编号设定在1~15之间,因为在这个范围内的元素仅消耗一个字节来编码,而162047需要消耗两个字节。[7]

repeated

repeated 对于的是各个语言中的数组结构,例如我想通过 gRPC 发送这样的一个数据

1
2
3
4
{
"字符串1": "fishfish.date",

}

参考资料


  1. Protocol Buffer Basics: Go | Protocol Buffers | Google Developers ↩︎ ↩︎

  2. 4.4 gRPC 入门 · Go 语言高级编程 ↩︎

  3. Language Guide (proto3) | Protocol Buffers | Google Developers ↩︎

  4. Go Generated Code | Protocol Buffers | Google Developers ↩︎

  5. Protobuf 的 import 功能在 Go 项目中的实践 - Go 语言中文网 - Golang 中文社区 ↩︎

  6. protoc-gen-go: add paths=source_relative option by neild · Pull Request #544 · golang/protobuf ↩︎

  7. Language Guide | Protocol Buffers | Google Developers ↩︎