【GO】protobuf在golang中的测试用例

news2024/9/24 1:22:21

上篇文章介绍了如何安装protobuf环境,文章链接如下

【Go】protobuf介绍及安装-CSDN博客

本节介绍protobuf在gRPC中具体如何使用,并编写测试用例

一、Protobuf是如何工作的

  .proto文件是protobuf一个重要的文件,它定义了需要序列化数据的结构,当protobuf编译器(protoc)来运行.proto文件时候,编译器将生成所选择的语言的代码,比如你选择go语言,那么就会将.proto转换成对应的go语言代码,对于go来说,编译器会为每个消息类型生成一个pd.go文件,而C++会生成一个.h文件和一个.cc文件。

  使用protobuf的3个步骤是:

    1. 在.proto文件中定义消息格式。

    2. 用protobuf编译器编译.proto文件。

    3. 用C++/Java/go等对应的protobuf API来写或者读消息。

二、Protobuf代码测试

在开始代码编写与测试之前,把官网的链接分享给大家,这个看完可以避坑,尤其是版本,示例代码,proto文件格式等。

工具安装及demo测试:Quick start | Go | gRPC

1.定义proto文件

syntax="proto3";
option go_package="./;student"; //关于最后生成的go文件是处在哪个目录哪个包中,.代表在当前目录生成,student代表了生成的go文件的包名是student

service DemoService {
  rpc Sender(StudentRequest) returns (StudentResponse){}
}

message StudentRequest {
  string Id = 1;
}

message StudentResponse {
  string result =1;
}

message Student {
  int64 Id = 1; //id
  string Name =2; //姓名
  string No =3; //学号
}

2.生成代码

进入proto文件所在目录,cd ~/sourceCode/go/goproject01/src/day34/grpc/proto

<1>执行protoc --go_out=. student.proto

protoc --go_out=. student.proto

执行后发现proto目录生成了一个文件:student.pb.go

<2>执行protoc --go-grpc_out=. student.proto,发现命令执行报错如下

cd ~/sourceCode/go/goproject01/src/day34/grpc/proto
protoc --go-grpc_out=. student.proto 
protoc-gen-go-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--go-grpc_out: protoc-gen-go-grpc: Plugin failed with status code 1.

执行报错,发现没有安装protoc-gen-go-grpc,需要安装一下

先执行go get

go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
go: downloading google.golang.org/grpc v1.59.0
go: downloading google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
go: downloading google.golang.org/protobuf v1.28.1
go: added google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
go: added google.golang.org/protobuf v1.28.1

再执行go install

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

执行完在$GOBIN目录下生成protoc-gen-go-grpc,源码对应在pkg下

再次执行protoc --go-grpc_out=. student.proto

protoc --go-grpc_out=. student.proto

执行后会在当前目录生成一文件:student_grpc.pb.go

<3>执行go mod tidy

打开文件发现依赖的包没有导入,会报错,需要执行一下最小化导入包依赖

go mod tidy
go: finding module for package google.golang.org/grpc
go: finding module for package google.golang.org/grpc/status
go: finding module for package google.golang.org/grpc/codes
go: found google.golang.org/grpc in google.golang.org/grpc v1.59.0
go: found google.golang.org/grpc/codes in google.golang.org/grpc v1.59.0
go: found google.golang.org/grpc/status in google.golang.org/grpc v1.59.0
go: downloading google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d
go: downloading golang.org/x/text v0.12.0

执行后生成的代码编译通过,不再报错。

3.编写Server端程序

在server包下创建server.go文件

package main

import (
	"context"
	"encoding/json"
	"errors"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"
	student "goproject01/day34/grpc/proto"
	"log"
	"net"
	"strconv"
	"time"
)

// grpc生成源码后多了一个方法mustEmbedUnimplementedDemoServiceServer
// 这个方法首字母小写不允许重载,自定义实现却没法实现该方法,解决方法如下
/**
1,生成代码时候使用选项:
protoc --go_out=. **--go-grpc_opt=require_unimplemented_servers=false** --go-grpc_out=. proto/*.proto
This works, but your binary will fail to compile if you add methods to your service(s) and regenerate/recompile.
That is why we have the embedding requirement by default. We recommend against using this option.
We recommend against using this option(不推荐使用此选项)

2,使用内嵌的结构体定义
// server is used to implement helloworld.GreeterServer.
type server struct{
// Embed the unimplemented server
helloworld.UnimplementedGreeterServer
}

*/
type MyDemeServiceImpl struct {
	student.UnimplementedDemoServiceServer
}

func (ds *MyDemeServiceImpl) Sender(ctx context.Context, in *student.StudentRequest) (*student.StudentResponse, error) {
	return handSendMessage(ctx, in)
}

func main() {
	//绑定9091端口
	listener, err := net.Listen("tcp", ":10005")
	if err != nil {
		log.Fatalf("bingding port:9091 error:%v", err)
	}

	//注册服务
	//这个连接最大的空闲时间,超过就释放,解决proxy等到网络问题(不通知grpc的client和server)
	/**
	func NewGrpcServer(opts ...grpc.ServerOption) *grpc.Server {
		var options []grpc.ServerOption
		options = append(options,
			grpc.KeepaliveParams(keepalive.ServerParameters{
				Time:    10 * time.Second, // wait time before ping if no activity
				Timeout: 20 * time.Second, // ping timeout
			}),
			grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
				MinTime:             60 * time.Second, // min time a client should wait before sending a ping
				PermitWithoutStream: true,
			}),
			grpc.MaxRecvMsgSize(Max_Message_Size),
			grpc.MaxSendMsgSize(Max_Message_Size),
		)
		for _, opt := range opts {
			if opt != nil {
				options = append(options, opt)
			}
		}
		return grpc.NewServer(options...)
	}
	*/
	option1 := grpc.KeepaliveParams(keepalive.ServerParameters{MaxConnectionIdle: 5 * time.Minute})
	option2 := grpc.MaxSendMsgSize(409600) //400kB
	option3 := grpc.MaxRecvMsgSize(409600)
	grpcServer := grpc.NewServer(option1, option2, option3)
	//impliServer := student.UnimplementedDemoServiceServer{}
	var impliServer = &MyDemeServiceImpl{}
	student.RegisterDemoServiceServer(grpcServer, impliServer)
	log.Printf("server listening at %v", listener.Addr())
	/*
		错误的写成http了,导致排查半天
		err = http.Serve(listener, nil)
			if err != nil {
				log.Fatalf("http serve fail:%v", err)
			}*/
	if err := grpcServer.Serve(listener); err != nil {
		panic("error building server: " + err.Error())
	}
}

func handSendMessage(ctx context.Context, req *student.StudentRequest) (*student.StudentResponse, error) {
	log.Println("receive param=", req.GetId())
	//模拟根据id查询student对象并构建一个student实例
	sid := req.GetId()
	if sid == "" {
		log.Println("request param id is null")
		return nil, errors.New("request param id is null")
	}
	resp := &student.StudentResponse{}
	sidInt64, err := strconv.ParseInt(sid, 10, 64)
	if err != nil {
		log.Printf("sid:%s covert to int64 error", sid)
		return nil, errors.New("sid covert to int64 error")
	}
	//通过proto进行序列化对象,和原始json以及easyJson使用方法类似
	s := &student.Student{Name: "xiaoliu", No: "10001", Id: sidInt64}

	//bytes, errs := proto.Marshal(s) //需要一个指针类型对象
	bytes, errs := json.Marshal(s)
	if errs != nil {
		log.Println("student obj convert to json error")
		return nil, errors.New("student obj convert to json error")
	}
	resp.Result = bytes
	log.Println("返回客户端序列化字符串:", string(bytes))
	return resp, nil
}

4.编写客户端程序

package main

import (
	"context"
	"flag"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	student "goproject01/day34/grpc/proto"
	"log"
	"time"
)

const (
	defaultName = "world"
	defaultId   = "10001"
)

var (
	address = flag.String("address", "localhost:10005", "the address connect to ")
	name    = flag.String("name", defaultName, " name to great")
	id      = flag.String("id", defaultId, "id send to server")
)

func main() {

	flag.Parse()
	connection, err := grpc.Dial(*address, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("connect localhost:9091 fail:%v\n", err)
	}
	defer connection.Close()

	client := student.NewDemoServiceClient(connection)
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	resp, errs := client.Sender(ctx, &student.StudentRequest{Id: *id})
	if errs != nil {
		log.Fatalf("client call server Sender method fail:%v\n", errs)
	}
	//获取StudentResponse result的内容
	rst := string(resp.GetResult())
	log.Println("rpc returns result:", rst)

}

5.代码测试

<1>启动服务端程序

go run server.go
//启动后打开服务端端口,等待客户端连接日志
2023/12/04 18:24:17 server listening at [::]:10005

//启动后接收客户端的参数打印
2023/12/04 18:24:25 receive param= 10001
2023/12/04 18:24:25 返回客户端序列化字符串: {"Id":10001,"Name":"xiaoliu","No":"10001"}

<2>运行客户端程序

go run client.go

首次执行发现报错如下:

rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: http2: frame too large"

错误解决:自己误把grpc协议写为http,修改代码即可:

/*
		错误的写成http了,导致排查半天
		err = http.Serve(listener, nil)
			if err != nil {
				log.Fatalf("http serve fail:%v", err)
			}*/
	if err := grpcServer.Serve(listener); err != nil {
		panic("error building server: " + err.Error())
	}

再次执行报错如下:

2023/12/04 18:09:55 client call server Sender method fail:rpc error: code = Internal desc = grpc: error while marshaling: string field contains invalid UTF-8
错误解决:需要修改student.proto文件中StudentResponse的result字段为bytes类型,用来支持utf-8字符。将student.proto文件修改如下:上面的server.go,client.go最终以这个proto文件为准。

syntax="proto3";
option go_package="./;student"; //关于最后生成的go文件是处在哪个目录哪个包中,.代表在当前目录生成,student代表了生成的go文件的包名是student

service DemoService {
  rpc Sender(StudentRequest) returns (StudentResponse){}
}

message StudentRequest {
  string Id = 1;
}

message StudentResponse {
  bytes result =1; //涉及到utf-8编码的字符需要使用bytes类型
}

message Student {
  int64 Id = 1; 
  string Name =2;
  string No =3;
}

修改后运行客户端程序:

调用服务端获取序列化的结果如下

go run client.go
2023/12/04 18:24:25 rpc returns result: {"Id":10001,"Name":"xiaoliu","No":"10001"}

6. gRPC官网文档

这里go官网提供使用gRPC开发步骤

Quick start | Go | gRPC

Basics tutorial | Go | gRPC

<1>hellworld测试:examples/helloworld

<2>route_guide测试:examples/route_guide

以上两个测试程序官网都有对应的文档,按照步骤测试执行即可。

7. 补充protobuf定义的数据类型

####

参考资料

gRPC介绍:​​​​​​Basics tutorial | Go | gRPC

Server到Client数据发送过程解析:gRPC 源码分析(四): gRPC server 中 frame 的处理 - 掘金

使用go实现gRPC:墨滴社区

HTTP/2:RFC7540:RFC 7540/7541: Hypertext Transfer Protocol Version 2 (HTTP/2)

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

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

相关文章

Ubuntu 20.0 + mysql 8.0 用户和密码修改

第一步 下载&#xff08;简单,注意联网&#xff09;Ubuntu 终端输入以下两行命令 (1) 数据库的服务端及客户端数据库的开发软件包 sudo apt-get install mysql-server mysql-client (2) 数据库的开发软件包 sudo apt-get install libmysqlclient-dev 第二步 查看是否安装成功 …

深度分析电动工具的发展趋势,盘点几个极具潜力的科技功能点

纵观市场发展规律&#xff0c;人类的每一次能源转型&#xff0c;都会带来大量红利商机&#xff0c;也会催生整个产业链的彻底革新。 一、电动工具的惊人爆发力 比如说电动工具这个大品类&#xff0c;在近两年意想不到地成为全球隐形增长冠军。主要原因在于海外市场有大量 DIY…

HNU-电路与电子学-2019期末A卷(不含解析)

【写在前面】 电路与电子学好像是从2020级开设的课程&#xff0c;故实际上目前只有2020与2021两个年级考过期末考试。 这门课程主要由所谓的“数电”与“模电”组成。而且先学的“模电”后学的“”数电&#xff0c;故期中考试主要以“模电”为主&#xff0c;期末考试主要以“数…

持续集成交付CICD:GitLab Webhook触发Jenkins流水线

目录 一、实验 1.Jenkins远程下载GiaLab仓库代码 2.curl远程触发Jenkins流水线 3.GitLab Webhook触发Jenkins流水线 二、问题 1.GitLab配置Webhook时报错 一、实验 1.Jenkins远程下载GiaLab仓库代码 (1) Jenkins添加选项参数 (2)添加字符参数 (3)查看构建参数情况 (4)添…

孩子都能学会的FPGA:第二十一课——用线性反馈移位寄存器实现伪随机序列

&#xff08;原创声明&#xff1a;该文是作者的原创&#xff0c;面向对象是FPGA入门者&#xff0c;后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门&#xff0c;作者不光让大家知其然&#xff0c;还要让大家知其所以然&#xff01;每个工程作者都搭建了全自动化的仿…

神经网络常用归一化和正则化方法解析(一)

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

CRM:提升营销效果的关键

一场成功的营销活动&#xff0c;可以帮助企业扩大知名度&#xff0c;获取大量的优质商机。作为专业的管理软件&#xff0c;CRM系统同样具备营销管理的能力&#xff0c;帮助企业实现营销活动的规划、执行和监控&#xff0c;提高营销效果。下面说说&#xff0c;CRM营销自动化对企…

职场人的年底总结,年初规划,又要开始啦!

在2023年&#xff0c;你可能错过了某些重要的职业发展机会&#xff0c;或者错失了一些与家人和朋友共度的时光。也可能经历了企业的降本增效&#xff0c;面临了被否定和裁员的风险&#xff1b;或者是得到了企业的重用和提拔&#xff0c;一个人撑起了整个业务部门。这些经历不仅…

Python中函数添加超时时间,Python中signal使用

from time import time, sleepimport signal# 模拟要删除5条数据,中间有超时的i 5# 超时后执行的方法def timeout_handler(signal, frame):# 引发异常raise TimeoutError("删除第" str(i) "条,超时!")# 或者执行其他操作,不往外抛异常(超时的函数不会被…

【排障记录】Oracle自动归档清理任务无法进行(windows平台),原因居然是?

前言 接用户求助&#xff0c;生产业务上的一套数据库存在归档文件占用过多磁盘空间的问题&#xff08;约四万个&#xff09;&#xff0c;需要清理&#xff0c;最好设置成定期自动清理&#xff0c;以减少人工干预。 处置过程 由于Oracle搭建在windows操作系统之上&#xff0c…

spring boot mybatis TypeHandler 源码如何初始化及调用

目录 概述使用TypeHandler使用方式在 select | update | insert 中加入 配置文件中指定 源码分析配置文件指定Mapper 执行query如何转换 结束 概述 阅读此文 可以达到 spring boot mybatis TypeHandler 源码如何初始化及如何调用的。 spring boot 版本为 2.7.17&#xff0c;my…

编译原理词法分析:NFA转DFA(原理+完整代码+可视化实现)

NFA转换为DFA 【本文内容摘要】 什么是DFA通过子集构造法将NFA转换为DFA生成DFA的dot文件并且形成可视化。 如果本文对各位看官有用的话&#xff0c;请记得给一个免费的赞哦&#xff08;收藏也不错&#xff09;&#xff01; 文章目录 NFA转换为DFA一、什么是DFA二、NFA转换为…

java表达式、java中jexl3的使用,java中jexl3如何自定义函数方法,jexl3自定义函数怎么传集合数组列表

引入jexl3 <dependency><groupId>org.apache.commons</groupId><artifactId>commons-jexl3</artifactId><version>3.2.1</version> </dependency> 基本用法 //引入对应包 import org.apache.commons.jexl3.*;public class …

【STM32入门】3.OLED屏幕

1.OLED引脚 OLED屏幕的接线按图所示&#xff0c;本例中用的是4管脚OLED屏幕 2.驱动程序 配套的驱动程序是“OLED.c"&#xff0c;主要由以下函数构成&#xff1a;1、初始化&#xff1b;2、清屏&#xff1b;3、显示字符&#xff1b;4、显示字符串&#xff1b;5、显示数字…

杀虫剂市场分析:2022年市场规模突破100亿元

杀虫剂是杀死害虫的一种药剂&#xff0c;目前杀虫剂主要应用在农业种植领域中&#xff0c;其中水稻的应用占比最大。杀虫剂的分类繁多&#xff0c;目前市场上主要以有机磷杀虫剂等为主流。 杀虫剂是指被用于杀死昆虫或防止昆虫进行破坏性行为的化学物质&#xff0c;杀虫剂的种类…

C盘爆满,python pip无法安装应用

解决方法1 C盘扩容 从其他盘压缩空间&#xff0c;C盘使用压缩的空间进行扩容&#xff0c;治标不治本&#xff0c;以后C盘还会越来越大 解决方法2 转移pip安装目录 1. 获取显示pip安装目录 C:\Users\biewang>pip show pip Name: pip Version: 23.3.1 Summary: The PyPA r…

品牌保护与知识产权:跨境电商中的法律挑战与解决方案

随着跨境电商的蓬勃发展&#xff0c;品牌保护和知识产权问题日益成为业界关注的焦点。在全球范围内进行电商业务&#xff0c;涉及到多国法律法规的复杂性&#xff0c;品牌所有者需要面对一系列法律挑战&#xff0c;保护其品牌和知识产权不受侵害。本文将深入探讨跨境电商中面临…

操作系统概论:揭秘计算机背后的神秘力量

操作系统概论 & 功能 概述定义操作系统功能作为系统资源的管理者向上层提供方便易用的服务作为最接近硬件的层次 主页传送门&#xff1a;&#x1f4c0; 传送 概述 概念&#xff1a; 定义 控制和管理计算机硬件和软件资源的程序一种系统软件为上层用户、应用程序提供简单易…

关于对ArrayBlockingQueue 的AQS探究

1、介绍 条件队列是 AQS 中最容易被忽视的一个细节。大部分时候&#xff0c;我们都用不上条件队列&#xff0c;但是这并不说明条件队列就没有用处了&#xff0c;它反而是我们学习生产者-消费者模式的最佳教材。条件队列是指一个阻塞队列&#xff0c;其中的元素是等待某个条件成…

派对的最大快乐值

与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 派对的最大快乐值 &#x1f48e;总结 派对的最大快乐值 题目 员工信息的定义如下&#xff1a; 公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、没有环的多叉树。树的头节点是公…