环境:
Golang: go1.18.2 windows/amd64
grpc: v1.47.0
protobuf: v1.28.0
完整代码:
https://github.com/WanshanTian/GolangLearning
cd GolangLearning/RPC/gRPC
1. 简介
gRPC
是一个基于C/S架构,使用protobuf
作为传输协议进行远程过程调用的高性能框架,前文【Golang | gRPC】protocol buffer compiler\protoc的安装,【Golang | gRPC】使用protoc编译.proto文件分别就protoc编译工具的安装和使用进行了详细的说明,下面通过一个demo具体说明gRPC的简单使用
2. 实践
现有下面一种场景:服务端保存着用户的年龄信息,客户端输入姓名,经RPC后获得对应的年龄
2.1 proto文件
2.1.1 新建gRPC文件夹,使用go mod init
初始化,创建pb文件夹,新建query.proto文件
syntax = "proto3";
package pb;
option go_package= ".;pb";
// 定义查询服务包含的方法
service Query {
rpc GetAge (userInfo) returns (ageInfo) {}
}
// 请求用的结构体,包含一个name字段
message userInfo {
string name = 1;
}
// 响应用的结构体,包含一个age字段
message ageInfo {
int32 age = 1;
}
服务端实现一个查询(Query)服务,包含一个方法GetAge
;从Golang层面理解,其实就是一个Query
接口,实现了一个GetAge
方法
2.1.2 在.\gRPC\pb
目录下使用protoc工具进行编译,在pb文件夹下直接生成.pb.go
和_grpc.pb.go
文件
protoc --go_out=./ --go-grpc_out=./ *.proto
2.2 pb.go和grpc.pb.go文件
2.2.1 查看query_grpc.pb.go
中生成的关于QueryServer
的定义
type QueryServer interface {
GetAge(context.Context, *UserInfo) (*AgeInfo, error)
mustEmbedUnimplementedQueryServer()
}
// UnimplementedQueryServer must be embedded to have forward compatible implementations.
type UnimplementedQueryServer struct {
}
func (UnimplementedQueryServer) GetAge(context.Context, *UserInfo) (*AgeInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAge not implemented")
}
func (UnimplementedQueryServer) mustEmbedUnimplementedQueryServer() {}
QueryServer
就是根据proto文件中定义的service Query{}
生成的接口,GetAge
是我们定义的方法,mustEmbedUnimplementedQueryServer()
这个方法是protoc自行编译出的(https://github.com/grpc/grpc-go/issues/3669这里有详细的讨论,在通过protoc编译时,也可以加参数取消这个方法,protoc --go_out=./ --go-grpc_out=require_unimplemented_servers=false:./ *.proto
)
2.2.2 查看pb.go中关于UserInfo
和AgeInfo
的定义
type UserInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *UserInfo) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type AgeInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"`
}
func (x *AgeInfo) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}
UserInfo
和AgeInfo
结构体的前三个字段暂且用不到,通过方法Get...
可以得到自定义字段的值
2.3 服务端
在gRPC目录下新建Server文件夹,新建main.go文件
2.3.1 下面我们通过Query
这个结构体具体实现QueryServer
接口
var userinfo = map[string]int32{
"foo": 18,
"bar": 20,
}
// Query 实现了QueryServer接口
type Query struct {
pb.UnimplementedQueryServer // 通过结构体嵌套的方式默认实现mustEmbedUnimplementedQueryServer()这个方法,查看2.2.1
}
func (q *Query) GetAge(ctx context.Context, info *pb.UserInfo) (*pb.AgeInfo, error) {
age := userinfo[info.GetName()]
var res = new(pb.AgeInfo)
res.Age = age
return res, nil
}
2.3.2 服务注册并启动
func main() {
// 创建socket监听器
listener, err := net.Listen("tcp", ":1234")
if err != nil {
log.Panic(err)
}
// new一个gRPC服务器,用来注册服务
grpcserver := grpc.NewServer()
// 注册服务方法
pb.RegisterQueryServer(grpcserver, new(Query))
// 开启gRPC服务
err = grpcserver.Serve(listener)
if err != nil {
log.Panic(err)
}
}
使用RegisterQueryServer
这个方法向gRPC服务器里注册服务。这里有两个服务,很容易概念搞混,一个是gRPC服务(用来监听,完成一些网络、路由等工作),一个是业务层面的服务(这些服务可以理解为包含一定方法的接口,用来给客户端进行调用)
2.4 客户端
在gRPC目录下新建Client文件夹,新建main.go文件
2.4.1 先建立无认证的连接,生成Client,然后进行方法调用
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"log"
"wanshantian/grpc/pb"
)
func main() {
//建立无认证的连接
conn, err := grpc.Dial(":1234", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Panic(err)
}
defer conn.Close()
client := pb.NewQueryClient(conn)
//RPC方法调用
age, _ := client.GetAge(context.Background(), &pb.UserInfo{Name: "foo"})
fmt.Println(age)
}
注:
- 使用
grpc.WithTransportCredentials(insecure.NewCredentials())
建立无认证的连接 - 使用
client := pb.NewQueryClient(conn)
生成gRPC客户端
运行结果如下:
age:18
3 总结
- 先创建
proto
文件,定义message
和service
- 根据编译生成的
pb.go
和grpc.pb.go
文件具体实现接口 - 服务端注册服务并启动,客户端建立连接进行方法调用