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

相关推荐
databook1 小时前
别让AI代码,变成明天的技术债
人工智能·程序员·ai编程
NineData4 小时前
从业务库到实时分析库,NineData构建MySQL到SelectDB同步链路
数据库·mysql·程序员
掉头发的王富贵8 小时前
【2026.3.28深圳腾讯云线下活动分享】我用龙虾3分钟完成了文章的抓取,写作,发布一条龙
程序员·线下活动·腾讯
阿里嘎多学长9 小时前
2026-03-27 GitHub 热点项目精选
开发语言·程序员·github·代码托管
我要改名叫嘟嘟9 小时前
一个大龄程序员的地铁日记(第8期),给予我影响,帮助我改变的书
程序员
陈随易10 小时前
农村程序员聊五险一金
前端·后端·程序员
badhope11 小时前
如何将小厂Java项目包装出高并发架构演进感
python·程序员·ipython
程序员鱼皮11 小时前
Claude 绝密模型泄露!Sora 关停、AI 工具链遭投毒… 本周最炸 AI 热点汇总
科技·ai·程序员·编程·ai编程
CodeSheep12 小时前
JetBrains又一知名软件宣布倒下,五味杂陈
前端·后端·程序员
SimonKing12 小时前
GitHub热榜1k星影视壳(OuonnkiTV)遇上AI影视源
java·后端·程序员