Ptotocol Buffers
1. Quick start#
Execute the following commands at your terminal, you don’t need to execute them under your project folder, just execute them anywhere, it will install the protoc
compiler and the protoc-gen-go
and protoc-gen-go-grpc
plugins into your $GOPATH/bin
folder.
# install protoc compiler
$ brew install protobuf
$ protoc --version # Ensure compiler version is 3+
# install plugins for
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Update your
$PATH
so that the protoc compiler can find the plugins:export PATH="$PATH:$(go env GOPATH)/bin"
2. Usage#
protoc
命令的基本用法是 protoc [OPTION] PROTO_FILES
, 其中 OPTION
包括搜索路径参数, 语言插件参数等, PROTO_FILES
是要编译的 .proto
文件. 可以通过 protoc --help
查看所有参数. 下面主要介绍 [OPTION]
参数.
2.1. 搜索路径参数#
-IPATH
或--proto_path=PATH
: 指定搜索.proto
文件的目录- 如:
-I.
表示在当前目录下搜索 - 如果不指定该参数,则默认在当前路径下进行搜索
- 如:
2.2. 语言插件 protoc-gen-go
#
protoc 本身支持多种语言, –cpp_out=,–python_out=等, 但不支持 Go 由google维护,需要安装 protoc 插件:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
之前介绍了 go install
会把 protoc-gen-go
安装到 $GOPATH/bin
目录下, 之后就可以在 protoc
命令中使用 --go_out
等参数了.
注意: protoc-gen-go 要求 pb (
.proto
) 文件中必须指定 go 包的路径, 例如option go_package = "github.com/shwezhu/consignment-service/proto/consignment"
. 文件结构:consignment-service/proto/consignment/consignment.proto
$ protoc I. --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
chat.proto
# or
$ protoc I. --go_out=. --go_opt=paths=import \
--go-grpc_out=. --go-grpc_opt=paths=import \
chat.proto
解释:
--go_out=.
- 作用: 指定go代码生成的基本路径
- 属于哪个插件:这个参数是为 protoc-gen-go 插件设置的
--go_opt=paths=source_relative
- 属于哪个插件:这同样是为 protoc-gen-go 插件设置的
- 一般有两个值:
paths=source_relative
和paths=import
paths=source_relative
: 例如你的.proto
文件位于proto/myapp/myproto.proto
, 若使用paths=source_relative
选项,生成的 Go 文件将会在类似myapp/myproto.pb.go
的路径下paths=import
: 例如你在myproto.proto
文件中指定了option go_package = "github.com/example/project/proto";
,那么无论.proto
文件的实际位置在哪里,生成的 Go 文件都将使用这个路径作为包路径
--go-grpc_out=.
- 属于哪个插件:这个参数是为 protoc-gen-go-grpc 插件设置的
-
--go-grpc_opt=paths=source_relative
- 属于哪个插件:这是为 protoc-gen-go-grpc 插件设置的
protoc-gen-go
vsprotoc-gen-go-grpc
: The old-way is usingprotoc-gen-go
that generates both serialization of the protobuf messages and grpc code. Nowprotoc-gen-go
no longer supports generating gRPC service definitions. For gRPC code, a new plugin calledprotoc-gen-go-grpc
was developed by Go gRPC project. source
2.3. grpc go 插件#
protoc-gen-go
纯粹用来生成pb(.proto
)序列化相关的文件, 不再承载 gRPC 代码生成功能, 生成gRPC相关代码需要安装 grpc-go 相关的插件 protoc-gen-go-grpc
, 安装命令如下:
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
插件 protoc-gen-go-grpc
也有两个参数, 类似 protoc-gen-go
插件的 --go_out
, --go_opt
参数, 分别是 --go-grpc_out
, --go-grpc_opt
, 用法也是一样的,
3. Example#
文件结构如下:
❯ tree -L 4
.
├── Makefile
├── go.mod
├── proto
│ └── calculator.proto
calculator.proto
:
syntax = "proto3";
// "./pb" 指定了生成的 Go 文件将被放置在相对路径 ./pb 的目录下
// 这里的相对路径是相对于 protoc 命令执行的目录, 而不是相对于 .proto 文件的目录
// protoc-gen-go 要求 `.proto` 文件必须指定 go 包的路径
option go_package = "./pb";
service Calculator {
rpc Add(CalculationRequest) returns (CalculationResponse);
rpc Subtract(CalculationRequest) returns (CalculationResponse);
rpc Multiply(CalculationRequest) returns (CalculationResponse);
rpc Divide(CalculationRequest) returns (CalculationResponse);
rpc Sum(NumbersRequest) returns (CalculationResponse);
}
message CalculationRequest {
int64 a = 1;
int64 b = 2;
}
message CalculationResponse { int64 result = 1; }
message NumbersRequest { repeated int64 numbers = 1; }
Makefile
:
# run: make generate
generate:
protoc --proto_path=proto proto/*.proto --go_out=. --go-grpc_out=.
# 1. 上面使用的所有路径都是相对 protoc 命令执行时所在的目录
# 2. --proto_path=proto: 指定 .proto 文件的搜索路径, 在这,编译器会在名为 proto 的目录下查找 .proto 文件,
# 你也可以使用 -IPATH 命令行参数来指定搜索路径 如 protoc -I. --go_out=. --go-grpc_out=. proto/*.proto
# proto/*.proto : 指定要编译的 .proto 文件,这里我们指定了 proto 目录下的所有 .proto 文件,
# 不指定会报错, 因为 protoc 需要知道要编译哪个文件, 你可以使用如 proto/xxx.proto
# 3. --go_out=.: 还记得我们在 .proto 文件中指定了 go_package="./pb" 吗,
# --go_out=. 为 go_package 指定了所在的工作目录, 举例:
# 若你使用 --go_out=abc, go_package="./pb", 则生成的文件会放在 abc/pb 目录下, 前提是你得先创建 abc 目录
# 我们在这指定的是 --go_out=., 所以最终生成的文件会放到 ./pb 目录下
执行 make generate
命令, 文件结构如下:
❯ tree -L 4
.
├── Makefile
├── go.mod
├── pb
│ ├── calculator.pb.go
│ └── calculator_grpc.pb.go
├── proto
│ └── calculator.proto
安装 grpc 包, 编写程序:
❯ go get google.golang.org/grpc
创建文件 server/main.go
(我省略了一些代码):
package main
type server struct {
pb.UnimplementedCalculatorServer
}
func (s *server) Add(
ctx context.Context,
in *pb.CalculationRequest,
) (*pb.CalculationResponse, error) {
return &pb.CalculationResponse{
Result: in.GetA() + in.GetB(),
}, nil
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalln("failed to create listener:", err)
}
s := grpc.NewServer()
// binds the Calculator service implementation to the gRPC server s.
pb.RegisterCalculatorServer(s, &server{})
if err := s.Serve(listener); err != nil {
log.Fatalln("failed to serve:", err)
}
}
最后文件结构如下:
❯ tree -L 4
.
├── Makefile
├── go.mod
├── go.sum
├── pb
│ ├── calculator.pb.go
│ └── calculator_grpc.pb.go
├── proto
│ └── calculator.proto
└── server
└── main.go
运行服务器:
❯ go run server/main.go