复杂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 main

import (
	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
		}
	}
	//不是已知的地名,返回一个没有命名的feature
	return &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.DialOption
	if *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 int32
	var lastPoint *pb.Point
	startTime := 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 points
	var points []*pb.Point
	for 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.mfbz.cn/a/229832.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

KaiwuDB 通过中国信通院“可信数据库”性能与稳定性评测

11月29日&#xff0c;中国信通院 2023 年下半年“可信数据库”评估评测结果正式发布&#xff0c;由 KaiwuDB研发的开务数据库系统 KaiwuDB V2.0 达到信通院时序数据库性能、稳定性测试标准。 至此&#xff0c;KaiwuDB已完成时序数据库基础能力、性能、稳定性全项评测&#xff…

Python Tacacs故障诊断记录

背景&#xff1a;客户现场说我们的设备在3A鉴权时失败&#xff0c;没有认证成功 第一步&#xff0c;先看下我们log 没有明显的错误记录&#xff0c;貌似认证成功了但是确提示认证失败&#xff0c;有点迷 第二步&#xff0c;家里搭建和现场一致的环境&#xff0c;模拟登录发现是…

《文存阅刊》期刊发表简介

《文存阅刊》以“深研文化创新&#xff0c;崇尚科学真理&#xff0c;坚持双百方针&#xff0c;打造学术精品”为办刊宗旨&#xff0c;涵盖艺术、文学、社科等多项内容&#xff0c;适应了文化市场需求&#xff0c;很好的回应了广大文化理论工作者的关切&#xff0c;为下一步打造…

cuda lib 线程安全的要义

1, 概述 cuda lib 线程安全的几个多线程的情景&#xff1a; 单卡多线程&#xff1b; 多卡多线程-每卡单线程&#xff1b; 多卡多线程-每卡多线程&#xff1b; 需要考虑的问题&#xff1a; 每个 cublasHandle_t 只能有一个stream么&#xff1f; 每个cusolverHandle_t 只能有一…

DTS认证

一、什么叫DTS DTS 是“Digital Theatre System“的缩写&#xff0c;是”数字化影院系统“的意思。是一种音频格式&#xff0c;从技术上讲&#xff0c;把音效数据存储到另外的CD-ROM中&#xff0c;使其与影像数据同步。这样不但空间得到增加&#xff0c;而且数据流量也可以相对…

如何运用gpt改写出高质量的文章 (1)

大家好&#xff0c;今天来聊聊如何运用gpt改写出高质量的文章 (1)&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff1a; 如何运用GPT改写出高质量的文章 一、引言 随着人工智能技术的飞速发展&#xff0c;自然…

HHDESK右键管理简介

在HHDESK管理文件&#xff0c;除了基本的打开、删除、复制、粘贴、重命名外&#xff0c;还有多种便捷编辑方式。 可以分别以下列模式打开文档&#xff1a; 文本模式即是以文本编辑器打开文档。 1 二进制模式 可进行二进制编辑。 2 JSON模式 可对JSON文件进行直观的解析…

b样条原理与测试

为了保留贝塞尔曲线的优点&#xff0c;同时克服贝塞尔曲线的缺点&#xff0c;b样条在贝塞尔曲线上发展而来&#xff0c;首先来看贝塞尔曲线的定义&#xff1a; 对于贝塞尔中的基函数而言&#xff0c;是确定的&#xff0c;全局唯一的&#xff0c;这导致了如果控制点发生变换将会…

软件测试--selenium安装使用

安装selenium不少人使用pip命令来安装selenium&#xff0c;辛辛苦苦安装完之后&#xff0c;还是不能使用。所以我们可以是直接使用编译器&#xff0c;pycharm直接安装selenium扩展包。 file中点击settings 在Settings中点击Project Interpreter,点击加号就可以安装各种需要的扩…

11.30BST理解,AVL树操作,定义;快速幂,二分求矩阵幂(未完)

完全二叉树结点的度可能有1&#xff0c;满二叉树的度只能为0或2 BST构建 BST是左孩子都比根节点小&#xff0c;右孩子都比根节点大 二叉搜索树的插入&#xff0c;删除&#xff0c;调整 平衡树理解 任何一个平衡二叉树&#xff0c;它的中序遍历都是一样的&#xff0c;都是有…

PRCD-1229 : An attempt to access configuration of database

今天维护oda一体机时&#xff0c;发现无法在grid用户下面关闭数据库实例&#xff0c;如下 ASM1:/home/gridoda0>srvctl stop database -d orcl -o immeidate PRCD-1229 : An attempt to access configuration of database orcl was rejected because its version 11.2.0.4.…

SpringBoot Seata 死锁问题排查

现象描述&#xff1a;Spring Boot项目&#xff0c;启动的时候卡住了&#xff0c;一直卡在那里不动&#xff0c;没有报错&#xff0c;也没有日志输出 但是&#xff0c;奇怪的是&#xff0c;本地可以正常启动 好吧&#xff0c;姑且先不深究为什么本地可以启动而部署到服务器上就无…

【代码随想录刷题】Day20 二叉树06

文章目录 1.【654】最大二叉树1.1 题目描述1.2 解题思路1.3 java代码实现1.4 总结 2.【617】合并二叉树2.1 题目描述2.2 解题思路2.3 java代码实现 3.【700】二叉搜索树中的搜索3.1 题目描述3.2 解题思路3.3 java代码实现 4.【98】验证二叉搜索树4.1 题目描述4.2 解题思路4.3 j…

卷积神经网络18种有效创新方法汇总,涵盖注意力机制、空间开发等7大方向

作为深度学习中非常重要的研究方向之一&#xff0c;卷积神经网络&#xff08;CNN&#xff09;的创新不仅表现在提升模型的性能上&#xff0c;还更进一步拓展了深度学习的应用范围。 具体来讲&#xff0c;CNN的创新架构和参数优化可以显著提高模型在各种任务上的性能。例如&…

如何跑AI模型—ultralytics

这里以跑 ultralytics 为示例&#xff0c;记录了如何从 0-1 跑个简单的模型&#xff0c;包括环境搭建。我的是 Window 系统&#xff0c;其他系统也类似。 主要流程是环境搭建&#xff0c;找个官网的 demo&#xff0c;收集好所需素材&#xff08;模型&#xff0c;图片等&#x…

Python入门第1篇

前言 很久之前就知道有python这个东西&#xff0c;当时也想的学学&#xff0c;不过一直没做行动派。 那时候就听说用python进行Excel数据分析处理、爬虫等很是厉害&#xff0c;但是始终没有与python的关系更进一步。 Python简介 用我自己的话说&#xff0c;python也是一门面…

k8s上安装KubeSphere

安装KubeSphere 前置环境安装nfs-server文件系统配置nfs-client配置默认存储创建了一个存储类metrics-server集群指标监控组件 安装KubeSphere执行安装查看安装进度 前置环境 下载配置我都是以CentOS 7.9 安装 k8s(详细教程)文章的服务器作为示例&#xff0c;请自行修改为自己的…

【评测脚本】机器信息评测(初版)

背景 QA的实际工作过程中,除了业务相关的测试外,也会涉及到一些评测相关的工作,甚至还要做多版本、多维度的评估分析。尤其是现在火热的大模型,相关的评测内容更是核心中的核心。当然本文的内容只是做一些初级的机器相关的评测信息,更多更广的评测需要更多时间的积累和总…

插入排序——直接插入排序和希尔排序(C语言实现)

文章目录 前言直接插入排序基本思想特性总结代码实现 希尔排序算法思想特性总结代码实现 前言 本博客插入排序动图和希尔排序视频参考大佬java技术爱好者&#xff0c;如有侵权&#xff0c;请联系删除。 直接插入排序 基本思想 直接插入排序是一种简单的插入排序法&#xff…

042:el-table表格表头自定义高度(亲测好用)

第042个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…
最新文章