原文链接:https://xiets.blog.csdn.net/article/details/144357277
版权声明:原创文章禁止转载
专栏目录:Golang 专栏(总目录)
文章目录
gRPC 相关链接:
- gRPC 官网:https://grpc.io/
- gRPC GitHub:https://github.com/grpc/grpc
- proto3 语言:https://protobuf.dev/programming-guides/proto3/
Go gRPC:
- Go gRPC 教程:https://grpc.io/docs/languages/go/
- Go gRPC 示例代码:https://github.com/grpc/grpc-go/tree/master/examples
1. gPRC 简介
RPC 是远程过程调用 (Remote Procedure Call) 的缩写形式,gRPC 最初是由 Google 创建的,高性能、开源通用、跨语言和平台 的 RPC 框架。
gRPC 使用 Protocol Buffers 定义服务,Protocol Buffers 是一个强大的二进制序列化工具集和语言。
2. 安装 gRPC
把 proto 文件编译生成 Go 代码,需要安装 Protocol Buffer 编译器 和 两个 Go 插件。
2.1 安装 Protocol Buffer 编译器
安装 Protocol Buffer 编译器安装:
- Linux 安装:
apt install -y protobuf-compiler
- MacOS 安装:
brew install protobuf
其他安装方式,参考:Protocol Buffer Compiler Installation
安装后将得到一个 protoc
命令工具:
$ protoc --help
2.2 安装 Go 插件
需要安装两个 Go 插件:
- protoc-gen-go-grpc:https://pkg.go.dev/google.golang.org/grpc/cmd/protoc-gen-go-grpc
- protoc-gen-go:https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go
protoc-gen-go-grpc 工具用于在 gRPC 的 protobuf 定义文件中生成服务端的 Go 语言绑定(name_grpc.pb.go 文件)。
protoc-gen-go 是一个 protoc 插件,用于为 Protocol Buffer 语言的 proto2 和 proto3 版本生成 Go 代码(name.pb.go 文件)。
使用 go install 命令安装这两个插件:
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
安装后在 ${GOPATH}/bin
目录下将会多出两个命令工具:protoc-gen-go-grpc
、protoc-gen-go
。
运行 protoc
命令生成 Go 代码时,该命令内部将会在 PATH 环境变量中查找 protoc-gen-go-grpc
和 protoc-gen-go
这两个命令。因此需要确保 ${GOPATH}/bin
路径在 PATH 环境变量中:
export PATH="${PATH}:$(go env GOPATH)/bin"
3. gRPC 使用示例
创建一个名为 project1
的项目:
$ mkdir project1 && cd project1
$ go mod init xiets.com/project1
后面所有命令都将在 project1/ 目录下执行。
添加 gRPC 依赖模块:
$ go get -u google.golang.org/grpc
3.1 proto 文件定义服务
使用 proto3 语言定义服务,保存到 project1/protos/user.proto
文件:
syntax = "proto3"; // 指定语言版本
// 具体编程语言的参数。
// 生成 Go 代码的导包路径和包名, 必须包含 "." 或 "/"。
// 如果没有指定包名则默认使用路径的最后一个文件夹名作为包名。
// 也可以自定义包名, 自定义包名的格式为在路径后面加上分号和自定义包名, 如: "grpc_service/user;user2"。
option go_package = "grpc_service/user";
// 服务接口 (可以定义多个服务, 编译后映射为一个类/接口)
service UserServ {
// 服务方法 (可以定义多个方法, 编译后映射为一个类/接口的方法)
rpc Login (LoginRequest) returns (LoginReply);
}
// 消息对象, 请求参数 (编译后映射为一个类/对象)
message LoginRequest {
string username = 1; // 类/对象的字段 (从1开始编号, 最小编号是1, 可以不按数值顺序, 值不能重复)
string password = 2; // 一旦消息类型被使用, 字段编号就不能更改, 因为它标识了消息传输格式中的字段, 更改编号相当于删除旧字段再创建新字段
}
// 消息对象 (响应参数)
message LoginReply {
string message = 1;
}
3.2 编译 proto 文件
生成 Go 代码,参考:https://protobuf.dev/reference/go/go-generated/
编译 proto 文件,生成 客户端 和 服务端 代码:
$ protoc -I./protos \
--go-grpc_out=. \
--go_out=. \
--go-grpc_opt=paths=import \
--go_opt=paths=import \
./protos/user.proto
# -IPATH, --proto_path=PATH
# 在 proto 文件中需要被 import 引用的其他 proto 文件所在目录, 可以多次指定多个。
# import 操作将在此目录下查找。
#
# --go-grpc_out=OUT_DIR
# user_grpc.pb.go 源码文件输出目录, 最终输出路径为: "{OUT_DIR}/{paths}/user_grpc.pb.go",
# 其中 "{paths}" 与 --go-grpc_opt=paths 参数值有关, 详见后面解释。
#
# --go_out=OUT_DIR
# user.pb.go 源码文件输出目录, 最终输出路径为: "{OUT_DIR}/{paths}/user.pb.go",
# 其中 "{paths}" 与 --go_opt=paths 参数值有关, 详见后面解释。
#
# --go-grpc_opt=paths
# --go_opt=paths
# 源码路径 在 OUT_DIR 目录下的形式, 有两个值: "import"(默认) 和 "source_relative"
#
# import:
# 参数值形式:
# --go-grpc_opt=paths=import
# --go_opt=paths=import
# 源码保存路径使用导包路径, 即 go_package 参数定义的多级路径, 最终路径为: "{OUT_DIR}/{go_package}/*.pb.go"
#
# source_relative:
# 参数值形式:
# --go-grpc_opt=paths=source_relative
# --go_opt=paths=source_relative
# 源码保存路径使用 proto 文件的相对路径 (相对于 -I 参数指定的目录), 使用此方式, go_package 参数的值只用于确定包名。
# 例如 "-I./protos" 和 "./protos/aa/bb/user.proto", 最终路径为: "{OUT_DIR}/aa/bb/*.pb.go"
#
# ./protos/user.proto
# 要编译的 proto 文件, 可以指定多个, 也支持 "./protos/*.proto" 通配符形式。
protoc
命令的--xxx_out
和--xxx_opt
等类型的参数,如果不在protoc --help
列出的参数中,则将从 PATH 目录下查找名为protoc-gen-xxx
的插件命令,并把参数值传递给插件,通过插件生成相应的源码。前面安装的两个 Go 插件的作用就在这里。
编译后生成的文件目录结构:
project1
│── protos
│ └── user.proto
│── grpc_service
│ └── user
│ │── user.pb.go
│ └── user_grpc.pb.go
│── go.mod
└── go.sum
3.3 服务端代码
gRPC 服务端代码:project1/server/main.py
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
"net"
pb "xiets.com/project1/grpc_service/user"
)
func main() {
// 创建 TCP 监听
listen, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 创建 gRPC 服务
var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...)
// 注册 UserServ 服务实例到 gRPC 服务
userServ := &UserServ{}
pb.RegisterUserServServer(grpcServer, userServ)
// 在指定监听上运行 gGPC 服务 (阻塞至服务终止)
err = grpcServer.Serve(listen)
if err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
// UserServ 用于实现 UserServ 服务接口
type UserServ struct {
pb.UnimplementedUserServServer
}
func (u *UserServ) Login(ctx context.Context, request *pb.LoginRequest) (*pb.LoginReply, error) {
// 获取请求参数
username := request.GetUsername()
password := request.GetPassword()
// 处理请求 ...
log.Printf("UserServ.Login: username=%s, password=%s\n", username, password)
// 构建响应参数
reply := &pb.LoginReply{
Message: fmt.Sprintf("Hi: %s", request.GetUsername()),
}
return reply, nil
}
启动 gRPC 服务端:
$ go run server/main.go
3.4 客户端代码
gRPC 服务端代码:project1/client/main.go
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
pb "xiets.com/project1/grpc_service/user"
)
func main() {
// 创建 gRPC 连接
addr := "localhost:8080"
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
conn, err := grpc.Dial(addr, opts...)
if err != nil {
log.Fatalf("grpc dial error: %v", err)
}
// 延迟关闭连接
defer func(conn *grpc.ClientConn) {
_ = conn.Close()
}(conn)
// 创建 UserServ 客户端实例 (在指定连接上通信)
client := pb.NewUserServClient(conn)
// 创建请求对象
req := &pb.LoginRequest{
Username: "hello",
Password: "world",
}
// 调用请求, 返回响应
resp, err := client.Login(context.Background(), req)
if err != nil {
log.Fatalf("grpc call error: %v", err)
}
// 获取响应参数
log.Printf("resp: %s\n", resp.GetMessage())
}
运行 gRPC 客户端:
$ go run client/main.go