复杂gRPC之go调用go

1. 复杂的gRPC调用

我们使用了一个较为复杂的proto文件,这个文件的功能主要是用来定位的,详细内容可以看代码中的注解

syntax = "proto3";
//指定生成的所属的package,方便调用
option go_package = "./";
package routeguide;
// 由服务器导出的接口。
service RouteGuide {// 一个简单的 RPC。// 获取给定位置的地点。// 如果在给定位置没有地点,则返回一个空名称的地点。rpc GetFeature(Point) returns (Feature) {}// 一个服务器到客户端的流式 RPC。// 获取给定矩形内可用的地点。结果以流的方式提供,而不是一次性返回// (例如,在具有重复字段的响应消息中),因为矩形可能覆盖一个大面积,并包含大量地点。rpc ListFeatures(Rectangle) returns (stream Feature) {}// 一个客户端到服务器的流式 RPC。// 接受正在遍历的路线上的一系列点流,当遍历完成时返回一个 RouteSummary。rpc RecordRoute(stream Point) returns (RouteSummary) {}// 一个双向流式 RPC。// 在遍历路线时接受一系列发送的 RouteNotes,同时接收其他 RouteNotes(例如来自其他用户)。rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
// 点的表示采用纬度-经度对的 E7 表示法
// (度数乘以10的7次方并四舍五入到最近的整数)。
// 纬度应该在+/- 90度的范围内,经度应该在+/- 180度的范围内(包括边界)。
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;
}
// RouteNote 是在给定点发送的消息。
message RouteNote {// The location from which the message is sent.Point location = 1;// The message to be sent.string message = 2;
}
// 在响应 RecordRoute rpc 时收到 RouteSummary。
// 它包含接收到的个别点的数量,检测到的地点数量以及作为每个点之间距离的累积和的总距离。
message RouteSummary {// 接收到点的数量int32 point_count = 1;// 通过遍历路线时经过的已知地点数量int32 feature_count = 2;// 以米为单位的距离。int32 distance = 3;// 遍历所用的时间,以秒为单位。int32 elapsed_time = 4;
}

相比之前的文件来说,这个方法中定义了四种类型的方法。
● 简单的RPC接口
  ○ 客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样。
● 一个服务器到客户端的流式RPC
  ○ 客户端发送请求到服务器,拿到一个流去读取返回的消息序列。 客户端读取返回的流,直到里面没有任何消息。
● 一个客户端到服务端的流逝RPC
  ○ 客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。
● 一个双向流式RPC
  ○ 双向流式 RPC 是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如, 服务器可以在写入响应前等待接收所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。

对proto文件进行编译

protoc --go_out=plugins=grpc:. RouteGuide.proto

2. 服务端代码

2.1导包

里面有一个需要我们进行实现的方法,可以在编译后的proto文件中找到。

package mainimport (pb "complex_go_server_grpc/proto""context""encoding/json""flag""fmt""io""log""math""net""os""sync""time""github.com/golang/protobuf/proto""google.golang.org/grpc"
)// type RouteGuideServer interface {
// 	// 一个简单的 RPC。
// 	// 获取给定位置的地点。
// 	// 如果在给定位置没有地点,则返回一个空名称的地点。
// 	GetFeature(context.Context, *Point) (*Feature, error)
// 	// 一个服务器到客户端的流式 RPC。
// 	// 获取给定矩形内可用的地点。结果以流的方式提供,而不是一次性返回
// 	// (例如,在具有重复字段的响应消息中),因为矩形可能覆盖一个大面积,并包含大量地点。
// 	ListFeatures(*Rectangle, RouteGuide_ListFeaturesServer) error
// 	// 一个客户端到服务器的流式 RPC。
// 	// 接受正在遍历的路线上的一系列点流,当遍历完成时返回一个 RouteSummary。
// 	RecordRoute(RouteGuide_RecordRouteServer) error
// 	// 一个双向流式 RPC。
// 	// 在遍历路线时接受一系列发送的 RouteNotes,同时接收其他 RouteNotes(例如来自其他用户)。
// 	RouteChat(RouteGuide_RouteChatServer) error
// }

2.2普通调用

服务端代码:

// 查询某个点位是否是已知的地名
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {for _, feature := range s.savedFeatures {if proto.Equal(feature.Location, point) {return feature, nil}}//不是已知的地名,返回一个没有命名的featurereturn &pb.Feature{Location: point}, nil
}

客户端代码:

// 获取给定点的特征。
func printFeature(client pb.RouteGuideClient, point *pb.Point) {// 打印日志,获取给定点的特征log.Printf("获取点 (%d, %d) 的特征", point.Latitude, point.Longitude)// 创建带有超时的上下文ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()// 调用 gRPC 客户端的 GetFeature 方法获取特征feature, err := client.GetFeature(ctx, point)if err != nil {// 处理获取特征失败的情况log.Fatalf("client.GetFeature 失败:%v", err)}// 打印获取到的特征log.Println(feature)
}

main函数

func main() {flag.Parse()var opts []grpc.DialOptionif *tls {if *caFile == "" {//*caFile = data.Path("x509/ca_cert.pem")}creds, err := credentials.NewClientTLSFromFile(*caFile, *serverHostOverride)if err != nil {log.Fatalf("Failed to create TLS credentials: %v", err)}opts = append(opts, grpc.WithTransportCredentials(creds))} else {opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))}//建立连接conn, err := grpc.Dial(*serverAddr, opts...)if err != nil {log.Fatalf("fail to dial: %v", err)}defer conn.Close()//根据连接创建客户端client := pb.NewRouteGuideClient(conn)//========利用客户端进行调用=========// 查看一个有效的点printFeature(client, &pb.Point{Latitude: 409146138, Longitude: -746188906})// 查看一个无效的点//printFeature(client, &pb.Point{Latitude: 0, Longitude: 0})// Looking for features between 40, -75 and 42, -73.// printFeatures(client, &pb.Rectangle{// 	Lo: &pb.Point{Latitude: 400000000, Longitude: -750000000},// 	Hi: &pb.Point{Latitude: 420000000, Longitude: -730000000},// })// RecordRoute//runRecordRoute(client)// RouteChat//runRouteChat(client)
}

效果:
在这里插入图片描述

2.3 服务端到客户端流式输出

服务端代码:

// 一个服务器到客户端的流式 RPC。
// 判断服务器端端地点是否有在距形范围内的
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {for _, feature := range s.savedFeatures {if inRange(feature.Location, rect) {if err := stream.Send(feature); err != nil {return err}}}return nil
}

客户端代码:

// 列出在给定边界矩形内的所有特征。
func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {log.Printf("Looking for features within %v", rect)ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()stream, err := client.ListFeatures(ctx, rect)if err != nil {log.Fatalf("client.ListFeatures failed: %v", err)}for {feature, err := stream.Recv()if err == io.EOF {break}if err != nil {log.Fatalf("client.ListFeatures failed: %v", err)}log.Printf("Feature: name: %q, point:(%v, %v)", feature.GetName(),feature.GetLocation().GetLatitude(), feature.GetLocation().GetLongitude())}
}

在这里插入图片描述

2.4 客户端到服务端流式输入

服务端代码:

// 接收来自客户端的一系列点流,计算路线的总点数、特征点数、覆盖距离和总耗时,
// 然后通过流式响应将计算结果发送回客户端。
func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {var pointCount, featureCount, distance int32var lastPoint *pb.PointstartTime := time.Now()// 循环接收来自客户端的点流for {// 接受点流中的点point, err := stream.Recv()// 如果点流结束,发送计算结果并关闭流if err == io.EOF {endTime := time.Now()return stream.SendAndClose(&pb.RouteSummary{PointCount:   pointCount,FeatureCount: featureCount,Distance:     distance,ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),})}// 处理接收点流过程中的错误if err != nil {return err}// 增加总点数pointCount++// 遍历保存的特征点,如果点匹配,则增加特征点数for _, feature := range s.savedFeatures {if proto.Equal(feature.Location, point) {featureCount++}}// 计算覆盖距离if lastPoint != nil {distance += calcDistance(lastPoint, point)}// 更新上一个点lastPoint = point}
}

客户端代码:

func runRecordRoute(client pb.RouteGuideClient) {// 随机生成一系列的点r := rand.New(rand.NewSource(time.Now().UnixNano()))pointCount := int(r.Int31n(100)) + 2 // Traverse at least two pointsvar points []*pb.Pointfor i := 0; i < pointCount; i++ {points = append(points, randomPoint(r))}log.Printf("Traversing %d points.", len(points))//============开始发送点流==============ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()stream, err := client.RecordRoute(ctx)if err != nil {log.Fatalf("client.RecordRoute failed: %v", err)}for _, point := range points {//遍历点并一个一个发送过去if err := stream.Send(point); err != nil {log.Fatalf("client.RecordRoute: stream.Send(%v) failed: %v", point, err)}}//等待结束并关闭reply, err := stream.CloseAndRecv()if err != nil {log.Fatalf("client.RecordRoute failed: %v", err)}log.Printf("Route summary: %v", reply)
}

效果:
在这里插入图片描述

2.5 双向流式RPC

服务端代码:

// RouteChat 接收一系列消息/位置对的流,并响应包含每个位置的所有先前消息的流。
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {for {// 从流中接收消息/位置对in, err := stream.Recv()// 如果流结束,返回 nil 表示成功处理if err == io.EOF {return nil}// 处理接收流过程中的错误if err != nil {return err}// 根据位置序列化消息/位置对,用作路由记录的键key := serialize(in.Location)// 使用互斥锁以确保并发安全s.mu.Lock()// 将接收到的消息添加到路由记录中s.routeNotes[key] = append(s.routeNotes[key], in)// 注意:此处的复制防止在为此客户端服务时阻塞其他客户端。// 我们不需要进行深度复制,因为切片中的元素是仅插入,永远不会修改的。rn := make([]*pb.RouteNote, len(s.routeNotes[key]))copy(rn, s.routeNotes[key])// 解锁互斥锁s.mu.Unlock()// 将之前一个地点的所有先前的消息发送回流for _, note := range rn {if err := stream.Send(note); err != nil {return err}}}
}

客户端代码:

// runRouteChat 接收一系列路由信息,同时为不同的位置发送信息。
func runRouteChat(client pb.RouteGuideClient) {// 预定义一组路由信息notes := []*pb.RouteNote{{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "First message"},{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Second message"},{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Third message"},{Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "Fourth message"},{Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Fifth message"},{Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Sixth message"},}// 创建一个带有超时的上下文ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()// 使用 RouteChat 方法创建流式 RPC 客户端stream, err := client.RouteChat(ctx)if err != nil {log.Fatalf("client.RouteChat 失败:%v", err)}// 创建一个等待信号的通道waitc := make(chan struct{})// 启动协程监听流式响应go func() {for {in, err := stream.Recv()// 如果流结束,关闭等待信号通道并返回if err == io.EOF {close(waitc)return}// 处理接收流过程中的错误if err != nil {log.Fatalf("client.RouteChat 失败:%v", err)}// 打印接收到的消息和位置log.Printf("收到消息 %s 在点(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)}}()// 遍历预定义的注释并通过流发送for _, note := range notes {if err := stream.Send(note); err != nil {log.Fatalf("client.RouteChat: stream.Send(%v) 失败:%v", note, err)}}// 关闭发送流stream.CloseSend()// 等待流结束的信号<-waitc
}

效果:
在这里插入图片描述

3 源代码链接

https://gitee.com/guo-zonghao/complex_go_server_grpc

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

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

相关文章

Redis和MySQL双写一致性实用解析

1、背景 先阐明一下Mysql和Redis的关系&#xff1a;Mysql是数据库&#xff0c;用来持久化数据&#xff0c;一定程度上保证数据的可靠性&#xff1b;Redis是用来当缓存&#xff0c;用来提升数据访问的性能。 关于如何保证Mysql和Redis中的数据一致&#xff08;即缓存一致性问题…

labelme等标注工具/数据增强工具输出JSON文件格式检查脚本

标注的文件太多了&#xff0c;还有用数据增强工具生成了一票的新数据。在转换或使用训练时候会报错&#xff0c;错误原因是json中语法有问题&#xff0c;这样会中断程序运行&#xff0c;调试造成很大困扰。 检查确实最后有问题&#xff0c;多写了一次 写一个脚本&#xff0c;用…

Python-滑雪大冒险【附源码】

滑雪大冒险 《滑雪大冒险》是一款充满趣味性和挑战性的休闲竞技游戏&#xff0c;在游戏中&#xff0c;玩家将扮演一位勇敢的滑雪者&#xff0c;在雪山上展示他们的滑雪技巧&#xff0c;游戏采用2D图形界面&#xff0c;以第三人称视角呈现 运行效果&#xff1a;用方向键及方向键…

flask 数据库迁移可能出现的六大问题,生成requirements文件夹方式,flask项目复写,

今日任务 项目分级显示 — app — — admin 代表 — — auth 代表用户的点赞 评论 登录等等 — — blog 代表blog的网页 首先单独把auth运行出来 第一步 1. 生成requirements文件夹 2.在一个新的虚拟环境里面完成requirements依赖下载 3.完成项目的复写 1. 生成requ…

算术运算(这么简单?进来坐坐?)

先热热身 算术运算&#xff0c;也称为四则运算&#xff0c;包括加法、减法、乘法和除法。此外&#xff0c;算术运算还包括乘方和开方。 在算术中&#xff0c;加减被视为一级运算&#xff0c;乘除被视为二级运算&#xff0c;乘方和开方被视为三级运算。在一道算式中&#xff0c;…

网站导航栏下滑隐藏,上滑显示,效果杠杆,兼容性强

前言 导航栏是网站必不可少的一部分&#xff0c;那么&#xff0c;导航栏应该怎么样子实现&#xff0c;可以高效自定义兼容开发呢&#xff1f;当然&#xff0c;不仅要实现&#xff0c;而且还要实现导航栏顶部固定位置&#xff0c;下拉隐藏&#xff0c;稍微往上滑动就会出现&…

Python中的并发编程(2)线程的实现

Python中线程的实现 1. 线程 在Python中&#xff0c;threading 库提供了线程的接口。我们通过threading 中提供的接口创建、启动、同步线程。 例1. 使用线程旋转指针 想象一个场景&#xff1a;程序执行了一个耗时较长的操作&#xff0c;如复制一个大文件&#xff0c;我们希…

2022年第十一届数学建模国际赛小美赛D题野生动物贸易是否应长期禁止解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 D题 野生动物贸易是否应长期禁止 原题再现&#xff1a; 野生动物市场被怀疑是此次疫情和2002年SARS疫情的源头&#xff0c;食用野生肉类被认为是非洲埃博拉病毒的一个来源。在冠状病毒爆发后&#xff0c;中国最高立法机构永久性地加强了野…

【git教程】

目录 git与SVN的区别&#xff1a;集中式与分布式的区别Windows上安装Git创建版本库/仓库&#xff08;repository&#xff09;将文件添加到repository报错处理 查看仓库的状态版本回退工作区和暂存区管理和修改撤销修改删除文件远程仓库添加远程仓库警告解除本地和远程的绑定关系…

一文3000字从0到1用Python进行gRPC接口测试!

gRPC 是一个高性能、通用的开源RPC框架&#xff0c;其由 Google 主要面向移动应用开发并基于HTTP/2 协议标准而设计&#xff0c;基于 ProtoBuf(Protocol Buffers) 序列化协议开发&#xff0c;且支持众多开发语言。 自gRPC推出以来&#xff0c;已经广泛应用于各种服务之中。在测…

AI助力智慧农业,基于SSD模型开发构建田间作物场景下庄稼作物、杂草检测识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a;《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草检…

【C语言快速学习基础篇】之二控制语句、循环语句、隐式转换

文章目录 一、控制语句1.1、for循环1.2、while循环1.3、注意&#xff1a;for循环和while循环使用上面等同1.4、do while循环1.4.1while条件成立时1.4.2、while条件不成立时 C语言介绍 C语言是一门面向过程的计算机编程语言&#xff0c;与C、C#、Java等面向对象编程语言有所不同…

“分割“安卓用户,对标iOS,鸿蒙崛起~

近期关于**“华为于明年推出不兼容安卓的鸿蒙版本”**的消息传出&#xff0c;引起了业界的热议关注。自从2019年8月&#xff0c;美国制裁下&#xff0c;华为不再能够获得谷歌安卓操作系统相关付费服务&#xff0c;如此情况下&#xff0c;华为“备胎”鸿蒙操作系统一夜转正。 华…

有效解决wordpress的502 Bad Gateway错误提示

摘要&#xff1a;最近有客户反映使用阿里云虚拟云主机&#xff0c;wordpress常提示502 Bad Gateway错误&#xff0c;网关错误是网站上遇到的常... wordpress的502 Bad Gateway错误如何修复&#xff1f; 第1步&#xff1a;偶发错误可尝试重新加载网站 偶尔出现流量突发爆增或是服…

Sql Server关于表的建立、修改、删除

表的创建&#xff1a; &#xff08;1&#xff09;在“对象资源管理器”面板中展开“数据库”节点&#xff0c;可以看到自己创建的数据库&#xff0c;比如Product。展开Product节点&#xff0c;右击“表”节点&#xff0c;在弹出的快捷菜单中选择“新建表”项&#xff0c;进入“…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion简介

Stable Diffusion是2022年发布的深度学习文本到图像生成模型,它主要用于根据文本的描述产生详细图像,尽管它也可以应用于其他任务,如

K-means算法通俗原理及Python与R语言的分别实现

K均值聚类方法是一种划分聚类方法&#xff0c;它是将数据分成互不相交的K类。K均值法先指定聚类数&#xff0c;目标是使每个数据到数据点所属聚类中心的总距离变异平方和最小&#xff0c;规定聚类中心时则是以该类数据点的平均值作为聚类中心。 01K均值法原理与步骤 对于有N个…

[HITCON 2017]SSRFme perl语言的 GET open file 造成rce

这里记录学习一下 perl的open缺陷 这里首先本地测试一下 发现这里使用open打开 的时候 如果通过管道符 就会实现命令执行 然后这里注意的是 perl 中的get 调用了 open的参数 所以其实我们可以通过管道符实现命令执行 然后这里如果file可控那么就继续可以实现命令执行 这里就…

JavaSE基础50题:12. 编写代码模拟三次密码输入的场景。

概述 编写代码模拟三次输入的场景&#xff0c;最多能输入三次密码&#xff0c;密码正确&#xff0c;提示 “登录成功” &#xff0c;密码错误&#xff0c;可重新输入&#xff0c;最多输入三次&#xff0c;三次均错&#xff0c;则提示退出程序。 代码 import java.util.Scann…

Redission分布式锁原理初探

什么是分布式锁&#xff0c;为什么需要分布式锁 在多线程并发请求当中&#xff0c;为了保证我们的资源同一时刻只有一个线程进行操作&#xff08;如商品超卖问题、购票系统等&#xff09;&#xff0c;我们通常要添加锁机制&#xff0c;如ReentrantLock&#xff0c;也就是可重入…