关于grpc的第一次尝试

news2025/1/13 10:16:28

自己瞎琢磨的,有错勿怪。

一、rpc理解

微服务会在不同的端口使用不同的语言提供相异的服务,端口之间的通信就使用rpc。这边的grpc的“g”我原先意味是golang,后来发现是google。

在golang关于rpc的官方包中,rpc主要有使用http/tcp协议,使用gob/json两个不同的维度,我之前的文章中也写过相关的例子。

grpc原先应该是cpp使用的,但是后来支持了不同的语言,特殊之处在于使用protobuf(似乎可以换成json但是我暂时没有看到这部分)作为通信的格式,此外通信方面使用的是HTTP2,所以我们会处理相关的stream,也就是流,能让我们的通信有更灵活的操作。

二、protobuf

首先可能要学习protobuf的相关知识,这里我就不写了,它的out路径方面我感觉还是有点复杂的。下面我把自己的例子放出来

syntax="proto3";
package test;//这个test其实会影响到生成的interface或者是client和server结构体的命名
option go_package="my_grpc_project/proto"; // 这是一个绝对路径的引入,从自己的项目路径开始 所以在编译的时候应该加上--go_opt=paths=source_relative 表示这个路径是相对的
import "test1.proto"; //得知import的文件应该和主文件在同一个文件夹下面
// 下面这结构体就是用来打印输出信息的
message QustMessage{
    string msg=1;
}
// 定义一下从简单到双向流的四种行为
service Test{
    rpc SimpleTest(QustMessage)returns(test1.ReplyMessage){}; //包名作为前缀,让其识别
    rpc ServerStream(QustMessage)returns(stream test1.ReplyMessage){};
    rpc ClientStream(stream QustMessage)returns(test1.ReplyMessage){};
    rpc BothStream(stream QustMessage)returns(stream test1.ReplyMessage){};
};

这里定义了一个QustMessage的信息对象,里面就一个msg的string。此外,我尝试了一下引入外部的test1.proto,test1.proto定义了ReplyMessage,如下:

syntax="proto3";
package test1;
option go_package="my_grpc_project/proto";
message ReplyMessage{
    string msg=1;
}

回到上文的protobuf文件,我在service当中定义了四种服务,分别是SimpleTest(发送一个请求消息,返回一个回复消息)、ServerStream(发送一个请求消息,服务器推送一堆消息)、ClientStream(客户端发送一堆请求消息,服务器返回一个消息)和BothStream(双方都发送一堆消息)。注意这里的行为更像是一种定义,没有写服务的实体,这些服务所对应的具体内容后期是需要你自己补充的。

这一步说一下,vscode可能对引入路径之类的产生报错,不要管,没事的,另外就是路径的问题,也就是go_package部分,我是建议全部写成“.”,也就是生成在本文件夹内。

然后我们需要使用指令生成相关的文件,这里生成的文件有两种,一中是简单的pd.go的文件,这种文件是把对应的protobuf转换成go的结构体,并附加一些对应的方法,比如我这边ReplyMessage当中有一个msg的string,会生成如下的方法让你获取对应的信息

func (x *ReplyMessage) GetMsg() string {
	if x != nil {
		return x.Msg
	}
	return ""
}

类似的还有String(输出)和Reset(重置ReplyMessage)的方法。

另外一个文件就是对应的test_grpc.pb.go这样类型的文件,这个文件里生成的内容会辅助你生成grpc的客户端和服务端。我会在下面具体谈一谈。

看到test_grpc.pb.go,你就应该知道protobuf生成文件名称的格式规则,就是protobuf中的package名+pb.go和package_grpc.pb.go。(注意protobuf中的package不是golang之中的package)

我这边没有写生成protobuf和grpc文件的终端代码,我觉得还是自己操作一下,贯通整个流程比较好

三、test_grpc.pb.go

如果你成功生成了这个文件,你可能需要看一看其中的长长的内容。这一部分其实有点难以表达。就拿BothStream作一个例子吧

客户端部分

首先是客户端部分,首先会创建一个整体的客户端,客户端调用BothStream方法

func (c *testClient) BothStream(ctx context.Context, opts ...grpc.CallOption) (Test_BothStreamClient, error) {
	stream, err := c.cc.NewStream(ctx, &Test_ServiceDesc.Streams[2], "/test.Test/BothStream", opts...)
	if err != nil {
		return nil, err
	}
	x := &testBothStreamClient{stream}
	return x, nil
}

这个方法生成了一个Test_BothStreamClient的接口和对应方法

type Test_BothStreamClient interface {
	Send(*QustMessage) error
	Recv() (*ReplyMessage, error)
	grpc.ClientStream
}

type testBothStreamClient struct {
	grpc.ClientStream
}

func (x *testBothStreamClient) Send(m *QustMessage) error {
	return x.ClientStream.SendMsg(m)
}

func (x *testBothStreamClient) Recv() (*ReplyMessage, error) {
	m := new(ReplyMessage)
	if err := x.ClientStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

实际上就是返回了一个testBothStreamClient,这结构体有Send和Recv方法以及grpc.ClientStream原有的方法。作为能发送stream的客户端,明显需要收发两个方法。

服务端

再看一下服务器端:

func _Test_BothStream_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(TestServer).BothStream(&testBothStreamServer{stream})
}

type Test_BothStreamServer interface {
	Send(*ReplyMessage) error
	Recv() (*QustMessage, error)
	grpc.ServerStream
}

type testBothStreamServer struct {
	grpc.ServerStream
}

func (x *testBothStreamServer) Send(m *ReplyMessage) error {
	return x.ServerStream.SendMsg(m)
}

func (x *testBothStreamServer) Recv() (*QustMessage, error) {
	m := new(QustMessage)
	if err := x.ServerStream.RecvMsg(m); err != nil {
		return nil, err
	}
	return m, nil
}

服务器端基本上也一样,也是接受两个方法,很合理,毕竟两边都是流,只是多了一个如下的内部函数

func _Test_BothStream_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(TestServer).BothStream(&testBothStreamServer{stream})
}

这个函数,我们将在服务器和客户端建立起来的时候再讨论。

其他的三个函数都是类似的,虽然方法上有点区别,可以通过查看test_grpc.pb.go去理解。

四、调用

客户端

首先是客户端发起调用,这部分直接跟着官网写,这里为了方便,依旧是调用BothStream

func main() {
	conn, err := grpc.Dial("localhost:996",         grpc.WithTransportCredentials(insecure.NewCredentials()))
	// 这上面的安全参数居然一定要写
	if err != nil {
		log.Fatal("dial fail: ", err)

	}
	defer conn.Close()
	testClient := pb.NewTestClient(conn)
	// 创建一个context
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	// 开始调用相关的函数
	
	// 调用第四个函数,这第四个函数采取的是一来一回的模式
	testBothStreamClient, err := testClient.BothStream(ctx)
	if err != nil {
		fmt.Println("both stream 调用失败:", err)
	}
	msgArr := []string{"你", "好", "啊"}
	index := 0
	giveout := func(index int) (err error) {
		if index > len(msgArr)-1 {
			err = io.EOF
			return
		}
		err = testBothStreamClient.Send(&pb.QustMessage{Msg: msgArr[index]})
		return
	}
	for {
		err1 := giveout(index)
		if err1 == io.EOF {
			testBothStreamClient.CloseSend()
			fmt.Println(err1)
			break
		}
		if err1 != nil {
			fmt.Println("1", err1)
			break
		}
		res4, err := testBothStreamClient.Recv()
		if err != nil {
			fmt.Println("close and Recv fail:", err)
		}
		fmt.Println("the forth func", res4.GetMsg())
		index++
	}
}

这边我写了一个giveout函数,用for循环去发送msgArr := []string{"你", "好", "啊"}字体,采取了一问一答的方式,接受服务器端发送的信息

服务器

下面是服务器端,照理照抄官网

package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"strconv"

	pb "my_grpc_project/proto"
	"net"

	"google.golang.org/grpc"
)

type server struct {
	pb.UnimplementedTestServer
}

// 第四个函数
func (s *server) BothStream(tbss pb.Test_BothStreamServer) (err error) {
	for {
		res, err := tbss.Recv()
		if err == io.EOF {
			fmt.Println("全部接受完成")
			break
		}
		if err != nil {
			fmt.Println("接受失败")
			break
		}
		err = tbss.Send(&pb.ReplyMessage{Msg: fmt.Sprintf("接收到你的消息:%s\n", res.GetMsg())})
		if err != nil {
			fmt.Println("发送信息失败")
			break
		}
	}
	return
}
func main() {
	listener, err := net.Listen("tcp", "localhost:996")
	if err != nil {
		log.Fatalln("listen fail: ", err)
	}
	var opt []grpc.ServerOption
	grpcServer := grpc.NewServer(opt...)
	pb.RegisterTestServer(grpcServer, &server{})
	fmt.Println("grpc客户端启动,正在localhost:996进行监听")
	err = grpcServer.Serve(listener)
	if err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

服务器端需要具体写一下对应的BothStream方法,服务器端接受请求的时候就会调用这个方法,我们上面还有一个问题,既然会调用我自己定义的方法,那么在test_grpc.pb.go当中为什么会有这个函数呢

func _Test_BothStream_Handler(srv interface{}, stream grpc.ServerStream) error {
	return srv.(TestServer).BothStream(&testBothStreamServer{stream})
}

以我目前的知识,我认为,应该和开启服务端的这个内容一起来看

pb.RegisterTestServer(grpcServer, &server{})

我在注册服务器的时候,将这个server带着自定义方法的server也传了进去,这应该对应的就是_Test_BothStream_Handler函数的srv参数,每次服务端调用实际的BothStream方法的时候,实际上调用的_Test_BothStream_Handler函数,而在这个函数之中调用srv中的你自己定义的方法。所以大概就是这样的一个流程。我还讲整个测试的内容传入了gitee,需要的可以看看,包含完整的四个函数的调用,当然这只是初步,并没有包含更细节的配置。my_grpc_project: grpc使用测试https://gitee.com/huangfengnt/my_grpc_project

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/145681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(7)Qt中的自定义槽(函数)

目录 槽函数定义的规则 关联信号与槽错误的示例 类成员函数函数做槽函数使用 静态类成员函数做槽函数使用 全局函数做槽函数使用 lambda表达式做槽函数使用 使用lambda表达式的注意事项 注意事项一: 注意事项二: 槽函数定义的规则 1.槽函数返回值…

多图解析KMP算法原理

KMP是什么 KMP是一种字符串匹配算法,能够判断字符串s2,是否为字符串s1的子串 例如:s1 "abd123def",s2 "123",KMP会返回4,代表s2是s1的子串,第一个匹配的下标为3 假设s…

线程池及源码分析

目录 1 java构建线程的方式 2 线程池的7个参数 3 线程池属性标识&线程池的状态 3.1 核心属性 3.2 线程池的状态 4 线程池的执行流程 5 添加工作线程的流程 6 Worker的封装&后续任务的处理 1 java构建线程的方式 一般就3~4种: 继承Thread&#xff…

迎接新年,暂且用Python绘制几个中国结吧

前言 今天就来分享几个用python绘制的图案吧 马上就要迎来新年了 就绘制了几个中国结,嘿嘿 话不多说,直接展示一下代码和效果图吧 更多学习资料与源码点击文章末尾名片领取 1. 效果图: 代码展示 import turtle turtle.screensize(600,…

GPDB插件安装工具之gppkg

gppkg命令gppkg是一个python3编写的打包脚本,在整个集群中安装.gppkg格式的Greenplum数据库扩展(例如PL/Java、PL/R和MADlib)及其依赖项,位于/usr/local/cloudberry-db/bin/gppkg(自己安装的gpdb目录),安装到$GPHOME里…

1个寒假能学多少网络安全知识?

现在可以看到很多标题都声称三个月内就可以转行网络安全领域,并且成为月入15K的网络工程师。那么,这个寒假的时间能学多少网络安全知识?是否能入门网络安全工程师呢? 答案是肯定的。 虽然网络完全知识是一门广泛的学科&#xff…

ccc-sklearn-13-朴素贝叶斯(1)

朴素贝叶斯 一种直接衡量标签和特征之间概率关系的有监督学习算法,专注分类的算法,基于概率论和数理统计的贝叶斯理论。在计算的过程中,假设特征之间条件独立,不进行建模,采用后验估计。 sklearn中的朴素贝叶斯 类含…

1-选择题练手

1.采用递归方式对顺序表进行快速排序,下列关于递归次数的叙述中,正确的是 A.每次划分后,先处理较长的分区可以减少递归次数 B.递归次数与初始数据的排列次序无关 C.每次划分后,先处理较短的分区可以减少递归次数 D.递归次数与…

DaVinci:键 - 外部蒙版

调色页面:键Color:Key在调色页面,可以轻松地从媒体池将某个片段拖至节点面板中,以作为外部蒙版。或者,在节点上右击选择“添加蒙版” Add Matte。若无附加,则可以选择本节点片段的明度信息作为外部蒙版。当…

hbase2.x orphan regions on filesystem(region丢失)问题修复

问题描述:orphan regions on filesystem 可以通过主master web页面的HBCK Report查看 也可以通过hbck2工具查看 # 查看指定表 hbase hbck -j $HBASE_HOME/lib/hbase-hbck2-1.3.0-SNAPSHOT.jar addFsRegionsMissingInMeta default:tableName # 查看命名空间下所有…

Yolov5+TensorRT-生成dll-python/c++调用dll

YOlov5-6.0TensorRTdllpython/c调用简介1.项目环境2.TensorRT验证1.在tensorrtx-yolov5-v6.0\yolov5目录下新建build目录2.编写CMake.txt,根据自己目录更改2(OpenCV_DIR)、3(TRT_DIR)、10(Dirent_INCLUDE_DIRS&#xf…

LabVIEW网络服务器何使用,有哪些不同

LabVIEW网络服务器何使用,有哪些不同NI有几款不同的Web服务器,可使用不同的产品并覆盖不同的用例。它们具有非常相似的名称,可以互换使用,但每个都提供不同的功能。应用程序Web服务器描述:NI应用Web服务器加载使用LabV…

企业微信商户号是什么?如何开通?

企业微信作为一款优秀的移动办公工具,与微信全方位打通,既可以与客户沟通交流,也可以在达成交易后直接进行对公收款,但是前提是要开通企业微信商户号。前言企业微信和微信都出自腾讯,而且企业微信全方位连接微信&#…

C#,图像二值化(16)——全局阈值的力矩保持算法(Moment-proserving Thresholding)及其源代码

1、力矩保持法 提出了一种基于矩保持原理的自动阈值选择方法。以这样的方式确定地计算阈值,即在输出画面中保留输入画面的时刻。实验结果表明,该方法可以将给定的图像阈值化为有意义的灰度级。该方法描述了全局阈值,但也适用于局部阈值。 A…

企业微信开发——企业内部自建应用开发(第二篇)---JS_SDK配置

企业微信如果想要使用企业微信的JS_SDK来实现拍照、定位等等功能,就需要预先在使用到的页面进行配置,当然你可以做全局配置。对于JS_SDK的配置设计前端和后端的统一配置。下面我来说明下具体的步骤。特别说明:1、企业微信有的接口需要配置wx.…

shader基础入门(1)

本文基于unity免费公开课“Hi Shader以及网络公开资料等书写”遵循开源协议。 MeshFilter网格过滤器 从海量资源中挑选适合的Mesh将他交给MeshRender MeshRenderer 网格渲染器 负责把MeshFilter丢过来的Mesh,绘制显示到我们的场景中 Material 材质球 Material…

多线程之死锁

目录: 1.什么是死锁? 2.可重入与不可重入 3.发生死锁的三个典型情况 4.发生死锁的四个必要条件 5.如何破除死锁? 1.什么是死锁? 谈到死锁,程序猿们都心存忌惮,因为程序一旦出现死锁,就会导…

深度学习训练营之鸟类识别

深度学习训练营之鸟类识别原文链接环境介绍前置工作设置GPU导入数据并进行查找数据处理可视化数据配置数据集残差网络的介绍构建残差网络模型训练开始编译结果可视化训练样本和测试样本预测原文链接 🍨 本文为🔗365天深度学习训练营 中的学习记录博客&am…

机器学习:如何解决类别不平衡问题

类别不平衡是一个常见问题,其中数据集中示例的分布是倾斜的或有偏差的。 1. 简介 类别不平衡是机器学习中的一个常见问题,尤其是在二元分类领域。当训练数据集的类分布不均时会发生这种情况,从而导致训练模型存在潜在偏差。不平衡分类问题的示…

【Unity云消散】理论基础:实现SDF的8SSEDT算法

距离元旦假期已经过去5天了(从31号算起!),接着开始学习! 游戏中的很多渲染效果都离不开SDF,那么SDF究竟是什么呢?到底是个怎么样的技术?为什么能解决那么多问题? 1 SD…