grpc-go 四种模式

164 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

gRPC是谷歌开发的、一个现代的、开源的、高性能的远程过程调用(RPC)框架,可以在任何地方运行。gRPC使客户端和服务器应用程序能够透明地通信,并简化了连接系统的构建。官方GitHub地址

gRPC支持多种语言,其中包括go语言的grpc-go

准备工作

在golang语言中使用grpc需要先安装一个protocol buffer的东西,安装完后会有个protoc的命令可以用。

Protobuf 是谷歌的一种二进制编码技术,grpc的通讯就是通过protobuf进行的。.proto文件就是rotobuf的定义文件。其官方语法连接

然后在下载两个go的插件。

$ 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

准本工作就完成了,参考官网地址: grpc.io/docs/langua…

接下来就是编写.proto文件,然后用protoc命令进行相关grpc的go文件生成。

最后就是把生成的go文件引入到项目里,进行使用。

四种模式

下面以官方的例子进行四种模式的讲解,官方讲解连接官方源码连接

proto文件编写

下面是官方的样例proto文件:route_guide.proto

关于一个路由指南的服务应用。

syntax = "proto3";

// go_package 设置了生成的go文件包名
// 更一般的定义方式是相对方式 go_package=".;routeguide"
// 在protoc生成命令中指定包的路径
option go_package = "google.golang.org/grpc/examples/route_guide/routeguide";

// grpc服务所用的包名,可自定义
package routeguide;

//定义一个服务 在这里叫路由指南
service RouteGuide {
  // 定义rpc服务
  // 模式1 客户端(参数)与服务端((返回值)都是非流式。
  // 获取指定坐标特征值。参数为坐标,返回值为特征值
  rpc GetFeature(Point) returns (Feature) {}
  
  // 模式2 服务器到客户端的流式RPC,参数为非流式,返回值为流式。
  // 获取给定矩形中的特征值。参数为矩形坐标返回值为流式数据,所有在该矩形面积中的坐标特征值。
  rpc ListFeatures(Rectangle) returns (stream Feature) {}

  // 模式3 客户端到服务端的流式RPC,参数为流式,返回值为非流式
  // 获取路由详情。参数为流式坐标(一系列坐标),返回一个路由摘要。
  rpc RecordRoute(stream Point) returns (RouteSummary) {}

  // 模式3 客户端和服务端双向流式RPC, 参数与返回值都是流式。
  // 路由交互。参数为一个流式路由信息,返回值为另一个路由信息。
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

// message数据类型,该类型可嵌套其他数据类型。
// 对于go来说会映射成为一个struct
// 经纬度坐标点
message Point {
  int32 latitude = 1; //维度
  int32 longitude = 2; //经度
}

// 经纬度表示的矩形,lo,hi为两个坐标。
message Rectangle {
  Point lo = 1;
  Point hi = 2;
}

// 坐标点特征值。
message Feature {
  string name = 1;
  Point location = 2;
}

// 路由信息。
message RouteNote {
  Point location = 1;
  string message = 2;
}

// 路由摘要
message RouteSummary {
  int32 point_count = 1;
  int32 feature_count = 2;
  int32 distance = 3;
  int32 elapsed_time = 4;
}

上述proto文件中已经做了备注。主要是一个关于路由的服务。

其中服务定义部分。定义了四种类型的rpc服务。

1 客户端与服务端都发送非流式数据。即函数参数与返回值都是非流式数据。

2 客户端发送非流式数据,服务端发送流式数据。即参数为非流式数据返回值为流式数据。

3 客户端发送流式数据,服务端发送非流式数据。即参数为流式数据,返回值为非流式数据。

4 客户端与服务端都发送流式数据。即参数与返回值均为流式数据。

看到这,关于grpc的四种模式已经清楚了,无非是服务端与客户端的流式非流式数据的组合。总共四种组合。

protobuf定义完了,接下来就是生成go文件。

在route_guide目录下运行如下生成命令:

protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    routeguide/route_guide.proto

运行完后会生成 route_guide.pb.go 和 route_guide_grpc.pb.go 文件。

.pb.go 与 _grpc.pb.go 是固定格式,前边的名字 route_guide与route_guide.proto文件名一致。

xxx.pb.go 包含数据类型数据。包括请求和响应数据类型。

xxx_grpc.pb.go 包含客户端与服务端相关的接口及方法。

接下来利用生成的go文件去实现一个服务端和一个客户端。

服务端实现

服务代码解析

服务端代码地址(github.com/grpc/grpc-g…)

服务端实现代码

1670132006451.png

其中 pb.UnimplementedRouteGuideServer为上一步中生成的代码。主要作用是确定自定义的结构体能够完全实现服务接口。其代码如下:

1670132289220.png

此处生成的这些server服务代码与之前定义的protobuf中的方法一一对应:

//模式一
//protobuf
rpc GetFeature(Point) returns (Feature) {}
//go
func (UnimplementedRouteGuideServer) GetFeature(context.Context, *Point) (*Feature, error) {

go中服务端入参为Point,为客户端发送过来的数据,返回值Feature为响应给服务端数据。

// 模式二
// protobuf
 rpc ListFeatures(Rectangle) returns (stream Feature) {}
// go
func (UnimplementedRouteGuideServer) ListFeatures(*Rectangle, RouteGuide_ListFeaturesServer) error 

go中Rectangle为服务端发送过来的数据,RouteGuide_ListFeaturesServer为流式数据对象,有Send方法可以调用,发送给前端数据。

//模式三
//protobuf
rpc RecordRoute(stream Point) returns (RouteSummary) {}
//go
func (UnimplementedRouteGuideServer) RecordRoute(RouteGuide_RecordRouteServer) error

go中RouteGuide_RecordRouteServer为流式对象,有两个函数 SendAndClose 和 Recv 函数,通过这两个函数与客户端交互。

//模式四
//protobuf
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
//go
func (UnimplementedRouteGuideServer) RouteChat(RouteGuide_RouteChatServer) error 

go中RouteGuide_RouteChatServer为流式对象,有两个函数 Send 和 Recv 函数,通过这两个函数与客户端交互。

通过比较protobuf和go中的函数,可以发现一个规律,所有涉及到流传输的都会被封装成一个对象,当成参数传给服务函数。

当然以上内容不需要去记忆,使用时候go函数是自动生成的,按照名字去找函数,然后在自己的结构体中去实现并使用。

具体功能实现可以去看源码进行学习。

服务启动流程

有个routeGuideServer结构体,如何启动一个rpc服务端呢? 只需要四步。

  1. 监听一个端口
    lis, err := net.Listen("tcp", 50051)
    
  2. 创建GRPC基础服务
    grpcServer := grpc.NewServer()
    
  3. 注册自定义服务到GRPC服务
    pb.RegisterRouteGuideServer(grpcServer, newServer())
    
  4. 启动服务
    grpcServer.Serve(lis)
    

源码如下:

1670136878587.png

客户端实现

客户端启动流程

  1. 连接服务端,获取连接对象。

conn, err := grpc.Dial(*serverAddr, opts...)

  1. 获取客户端对象。

client := pb.NewRouteGuideClient(conn)

  1. 发送数据,收到数据。

feature, err := client.GetFeature(ctx, point)

stream, err := client.ListFeatures(ctx, rect);
feature, err := stream.Recv()

stream, err := client.RecordRoute(ctx);
stream.Send(point);
reply, err := stream.CloseAndRecv();

stream, err := client.RouteChat(ctx);
in, err := stream.Recv();
stream.Send(note);
stream.CloseSend();

发送数据,处理收到的数据,四种模式。具体使用参照官方使用代码。

结语

首先介绍了protobuf及.proto编写。

然后介绍了生成go文件名方法。

最后以官方例子介绍了四种模式,及服务端与客户端的代码编写。