gRPC入门系列之3-stream模式

gRPC 入门系列之 3-stream 模式

gRPC 的流模式有三种:

  • 客户端流模式,客户端向服务端发送多条消息,发送完毕后等待服务端返回结果。这可用于数据上报等场景。
  • 服务端流模式,客户端向服务端发送一条消息,服务端向客户端返回多条消息。这可用于服务端向客户端推送消息的场景,比如实时发送股票行情。
  • 双向流模式,客户端和服务端都向对方发送多条消息。你可能会在聊天应用中使用到这种模式。

下面我们来实现相关代码。

proto 文件定义

首先,我们来编写 proto 文件,定义相关接口。我们在下面的示例中实现一个简单的 calculator 功能,分别实现加法、求平均值、生成多个随机数三种方法,分别使用客户端流、服务端流、双向流模式。

protobuf 复制代码
syntax = "proto3";

package proto;

option go_package = "git.gqnotes.com/guoqiang/grpcexercises/calculator/pb";

service CalculateService {
  // 求和-客户端流式
  rpc Sum(stream SumRequest) returns(SumResponse) {}
  // 生成一定数量的随机数-服务端流式
  rpc RandomNums(RandomNumsRequest) returns(stream RandomNumsResponse) {}
  // 双向流式求平均值
  rpc Average(stream AverageRequest) returns(stream AverageResponse) {}
}

message SumRequest {
  int64 num =1;
}


message SumResponse {
  int64 total = 1;
}

message RandomNumsRequest {
  int64 num =1;
}

message RandomNumsResponse {
  int64 num = 1;
}

message AverageRequest {
  int64 num =1;
}

message AverageResponse {
  float average = 1;
}

生成相关代码

我们修改一下 Makefile 文件,添加如下代码:

makefile 复制代码
calculator-gen:
	 protoc -Icalculator/proto/ --go_out=./calculator --go_opt=module=git.gqnotes.com/guoqiang/grpcexercises/calculator --go-grpc_out=./calculator --go-grpc_opt=module=git.gqnotes.com/guoqiang/grpcexercises/calculator calculator/proto/*.proto

执行 make calculator-gen,生成相关代码。

编写服务端代码

我们在 server 目录下新建相关 go 文件,编写服务端代码。我们先来实现 Sum 方法,代码如下:

go 复制代码
package main

import (
	"git.gqnotes.com/guoqiang/grpcexercises/calculator/pb"
	"io"
)

// Sum 计算和-客户端流式
func (s *Server) Sum(stream pb.CalculateService_SumServer) error {
	var sum int64

	for {
		req, err := stream.Recv()

		if err == io.EOF {
			break
		}

		if err != nil {
			return err
		}

		sum += req.Num
	}

	if err := stream.SendAndClose(&pb.SumResponse{Total: sum}); err != nil {
		return err
	}

	return nil
}

在上面的代码中,我们从 stream 中读取数据,如果 err 为 io.EOF,则结束循环,否则将读取到的数据累加到 sum 变量中。最后,我们将计算结果返回给客户端。

下面我们来实现 main 函数:

go 复制代码
package main

import (
	"git.gqnotes.com/guoqiang/grpcexercises/calculator/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
)

func main() {
	// 实现服务端逻辑,监听5633端口
	lis, err := net.Listen("tcp", ":5633")

	if err != nil {
		log.Fatalf("failed to listen: %v", err)
		return
	}

	s := grpc.NewServer()

	// 反射服务
	reflection.Register(s)

	// 注册服务
	pb.RegisterCalculateServiceServer(s, &Server{})

	log.Println("grpc server start")

	if err = s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
		return
	}
}

type Server struct {
	pb.UnimplementedCalculateServiceServer
}

如你所见,我们启动了一个 grpc 服务,使用的端口是 5633,并且进行了反射注册(这只是为了后面的测试,不是必须的)。

RandomNums 和 Average 方法的视线,大家可以访问代码仓库查看。

编写客户端代码

在 client 目录下新建相关 go 文件,编写客户端代码。我们先来实现 Sum 方法,代码如下:
Sum.go

go 复制代码
package main

import (
"context"
"git.gqnotes.com/guoqiang/grpcexercises/calculator/pb"
"io"
"log"
)

// Sum 计算和-客户端流式
func Sum(client pb.CalculateServiceClient) {
// 构造一个切片
nums := []int64{1, 2, 3, 4, 5}

    stream, err := client.Sum(context.Background())

    if err != nil {
    	log.Fatalf("failed to call: %v", err)
    	return
    }

    // 发送数据
    for i := 0; i < len(nums); i++ {
    	err = stream.Send(&pb.SumRequest{
    		Num: nums[i],
    	})

    	if err == io.EOF {
    		break
    	}

    	if err != nil {
    		log.Fatalf("failed to send: %v", err)
    		return
    	}
    }

    // 接收数据
    resp, err := stream.CloseAndRecv()

    if err != nil {
    	log.Fatalf("failed to recv: %v", err)
    }

    log.Printf("nums:%+v\tresp:%+v\n", nums, resp)

}

main.go

go 复制代码
package main

import (
	"git.gqnotes.com/guoqiang/grpcexercises/calculator/pb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
)

func main() {
	conn, err := grpc.Dial(":5633", grpc.WithTransportCredentials(insecure.NewCredentials()))

	if err != nil {
		log.Fatalf("failed to dial: %v", err)
		return
	}

	defer conn.Close()

	// 实例化客户端
	client := pb.NewCalculateServiceClient(conn)

	Sum(client)

	Average(client)
}

启动服务

我们修改一下 Makefile 文件,添加如下代码:

makefile 复制代码
calculator-build:
	 go build -o ./calculator/bin/server-osx ./calculator/server
	 go build -o ./calculator/bin/client-osx ./calculator/client

执行 make calculator-build,生成可执行文件。然后执行./calculator/bin/server-osx 启动服务端程序。

在一个新的终端中,执行./calculator/bin/client-osx 启动客户端程序,可以看到如下输出:

txt 复制代码
2023/12/03 10:32:02 nums:[1 2 3 4 5]    resp:total:15
2023/12/03 10:32:02 num:1       recv:average:1
2023/12/03 10:32:02 num:2       recv:average:1.5
2023/12/03 10:32:02 num:3       recv:average:2
2023/12/03 10:32:02 num:4       recv:average:2.5
2023/12/03 10:32:02 num:5       recv:average:3

在第一行,调用的是 Sum 方法,客户端输入了 5 个数字:1 2 3 4 5,服务端返回了这 5 个数字的和 15。而在后面的输出中,调用的是 Average 方法,客户端输入了 5 个数字:1 2 3 4 5,服务端返回的是已经输入的数字的平均值。

使用 evans 测试接口

在上面,我们是通过编写客户端程序来测试接口。如果不想编写客户端代码,指向测试接口,可以使用 evans。

我们先修改一下 Makefile 文件,添加如下代码:

makefile 复制代码
calculator-evans-reflect:
	evans -r repl --port 5633

注意,上面的 5633 是 grpc 服务监听的端口。

接下来,执行 make calculator-evans-reflect,启动 evans,看到如下输出:

txt 复制代码
gq@gqdeMacBook-Air grpcexercises % make calculator-evans-reflect
evans -r repl --port 5633

  ______
 |  ____|
 | |__    __   __   __ _   _ __    ___
 |  __|   \ \ / /  / _. | | '_ \  / __|
 | |____   \ V /  | (_| | | | | | \__ \
 |______|   \_/    \__,_| |_| |_| |___/

 more expressive universal gRPC client


127.0.0.1:5633>

我们再来温习一下几个 evans 的命令:

  • show package:显示包名。
  • package xx :使用 xx 包。
  • show service:显示服务名。
  • service xx:使用 xx 服务。
  • show message:显示消息名。
  • call xx:调用方法 xx。

我们先执行几个命令,查看一下 package 和 service:

txt 复制代码
127.0.0.1:5633> show package
+-------------------------+
|         PACKAGE         |
+-------------------------+
| grpc.reflection.v1      |
| grpc.reflection.v1alpha |
| proto                   |
+-------------------------+

127.0.0.1:5633> package proto

proto@127.0.0.1:5633> show service
+------------------+------------+-------------------+--------------------+
|     SERVICE      |    RPC     |   REQUEST TYPE    |   RESPONSE TYPE    |
+------------------+------------+-------------------+--------------------+
| CalculateService | Sum        | SumRequest        | SumResponse        |
| CalculateService | RandomNums | RandomNumsRequest | RandomNumsResponse |
| CalculateService | Average    | AverageRequest    | AverageResponse    |
+------------------+------------+-------------------+--------------------+

proto@127.0.0.1:5633> service CalculateService

proto.CalculateService@127.0.0.1:5633>

接下来,让我们分别测试一下 Sum、RandomNums 和 Average 方法。

Sum:

txt 复制代码
proto.CalculateService@127.0.0.1:5633> call Sum
num (TYPE_INT64) => 1
num (TYPE_INT64) => 2
num (TYPE_INT64) => 3
num (TYPE_INT64) =>
{
  "total": "6"
}

proto.CalculateService@127.0.0.1:5633>

当我们已经完成输入后,可以按Ctrl+D来结束输入,再次按Ctrl+D来退出evans。 }}

RandomNums:

txt 复制代码
proto.CalculateService@127.0.0.1:5633> call RandomNums
num (TYPE_INT64) => 3
{
  "num": "752"
}
{
  "num": "798"
}
{
  "num": "528"
}

proto.CalculateService@127.0.0.1:5633>

在上面的测试中,我们输入的参数是 3,服务端返回了三个随机数。

Average:

txt 复制代码
proto.CalculateService@127.0.0.1:5633> call Average
num (TYPE_INT64) => 5
num (TYPE_INT64) => {
  "average": 5
}
num (TYPE_INT64) => 2
num (TYPE_INT64) => {
  "average": 3.5
}
num (TYPE_INT64) => 3
num (TYPE_INT64) => {
  "average": 3.3333333
}
num (TYPE_INT64) =>

proto.CalculateService@127.0.0.1:5633>

在上面的测试中,我们分别输入了三个参数 5、2、3,服务端返回了三次,每次返回的都是已经输入的数字的平均值。

感兴趣的朋友,可以查看我的代码仓库地址。

本文由mdnice多平台发布

相关推荐
程序员cxuan4 小时前
讲真,智能路由这事儿,还得看基元律动
人工智能·后端·程序员
AI前端工程狮5 小时前
用户点了「取消」,我的服务进程退出了
人工智能·程序员
得物技术8 小时前
压测AI Agent 治理与评测调优实践
程序员·架构·测试
SimonKing10 小时前
Workbuddy太占C盘空间了,写个迁移工具迁走10G的数据
java·后端·程序员
挖掘狂人11 小时前
Token 都上双十一货架了,AI 电脑的账该怎么算?
大数据·程序员·ai编程
深蓝AI14 小时前
26.1% 的 Agent 技能带漏洞:skills 霸榜 GitHub 当天,NVIDIA 和 OpenAI 同时掏出安检仪
人工智能·程序员
程序员cxuan1 天前
离谱,AI 大佬顺手搓了个 YouTube ,而我顺手给他搓了个中文插件
人工智能·后端·程序员
爱勇宝1 天前
Claude Fable 5.2 发布后,开发者该关注什么
程序员·ai编程
挖掘狂人1 天前
AI 把代码写得飞快,然后呢?聊聊我验收生成代码的一套流程
人工智能·程序员·ai编程
codigger1 天前
AI 把代码写得飞快,然后呢?聊聊我验收生成代码的一套流程
ai·程序员·编程·#人工智能