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多平台发布

相关推荐
文心快码BaiduComate16 分钟前
文心快码Zulu,如何成为我眼里的“AI编码战士”
程序员
KaneLogger1 小时前
AI模型与产品推荐清单20250709版
人工智能·程序员·开源
G探险者2 小时前
语言只是压缩包:人类交流为何如此低效?
程序员
掘金安东尼4 小时前
蔚来 600 亿研发成本,信还是不信。。
面试·程序员·github
考虑考虑6 小时前
go中的Map
后端·程序员·go
redreamSo7 小时前
AI Daily | AI日报:微信支付 MCP,开启“对话即交易”时代; 镁伽科技:机器人独角兽冲刺IPO; 北大团队突破存算一体排序难题
程序员·aigc·资讯
AI大模型7 小时前
大模型炼丹术(二):从离散的token IDs到具有语义信息的embedding
程序员·llm
程序员鱼皮8 小时前
没听说过设计模式?保姆级教程来了!
计算机·程序员·开发·学习路线·自学
liangdabiao8 小时前
3分钟打造一个无敌的落地页Landing Page - 任何内容都完全自动化
程序员·github
Baihai_IDP9 小时前
AI 深度研究(Deep Research)原理解析
人工智能·程序员