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

上篇文章介绍了如何安装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"}

 

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

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

相关文章

为什么Nginx被称为反向代理

下图显示了 &#x1d41f;&#x1d428;&#x1d42b;&#x1d430;&#x1d41a;&#x1d42b;&#x1d41d; &#x1d429;&#x1d42b;&#x1d428;&#x1d431;&#x1d432; 和 &#x1d42b;&#x1d41e;&#x1d42f;&#x1d41e;&#x1d42b;&#x1d42c;&#…

Android : AndroidStudio开发工具优化

1.开启 gradle 单独的守护进程 Windows: 进入目录 C:\Users\Administrator\.gradle 创建文件&#xff1a; gradle.properties # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Settings specified in this file will override any Gradle s…

mac本地部署stable-diffusion

下载Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" ①输入“1”选择中科大版本&#xff0c;然后输入Y(YES)&#xff0c;直接输入开机密码&#xff08;不显示&#xff09;然后回车确认&#xff0c;开始下载 ②…

深圳市左下右上百度坐标

爬取百度POI的时候&#xff0c;别人的代码中有提到左下&#xff0c;右上坐标&#xff0c;但是没有说从哪里来&#xff0c;而且还是百度的坐标。 经纬度:左下角,右上角&#xff1a;113.529103,37.444122;115.486183,38.768031 墨卡托坐标:左下角,右上角&#xff1a;12638139.45,…

数据结构学习笔记——广义表

目录 一、广义表的定义二、广义表的表头和表尾三、广义表的深度和长度四、广义表与二叉树&#xff08;一&#xff09;广义表表示二叉树&#xff08;二&#xff09;广义表表示二叉树的代码实现 一、广义表的定义 广义表是线性表的进一步推广&#xff0c;是由n&#xff08;n≥0&…

Video Studio会声会影2024中文直装旗舰版

Corel Video Studio会声会影2024中文直装旗舰版是一款很流行的视频编辑处理软件&#xff0c;由于其简单易用&#xff0c;且功能不错&#xff0c;在国内拥有众多使用者&#xff0c;小编之前给大家分享过Corel Video Studio Ultimate会声会影2024旗舰版中文版&#xff0c;今天再为…

FPGA串口接收解帧、并逐帧发送有效数据——1

FPGA串口接收解帧、并逐帧发送有效数据 工程实现的功能&#xff1a;FPGA串口接收到串口调试助手发来的数据&#xff0c;将其数据解帧。判断到正确的帧头和帧尾之后&#xff0c;将有效数据存入rx_data中&#xff1b;另一方面发送端将有效数据逐帧发送出去。 参考&#xff1a;正…

梯度上升和随机梯度上升

目录 梯度上升算法&#xff1a; 代码&#xff1a; 随机梯度上升算法&#xff1a; 代码&#xff1a; 实验&#xff1a; 做图代码&#xff1a; 疑问&#xff1a; 1.梯度上升算法不适应大的数据集&#xff0c;改用随机梯度上升更合适。 2.改进过的随机梯度算法&#xff0…

边缘计算网关构建智慧楼宇新生态,打造未来建筑管理

边缘计算网关在无人值守环境中的应用十分广泛&#xff0c;尤其在智慧楼宇管理方面发挥着重要作用。它能够实现多个地点多楼宇之间的数据实时互通&#xff0c;通过边缘计算网关物联网应用构建智慧楼宇生态系统&#xff0c;解决传统楼宇管理网络布线、人员巡检以及后期运维等问题…

IoT DC3 是一个基于 Spring Cloud 全开源物联网平台 linux docker部署傻瓜化步骤

如有不了解可先参考我的另一篇文章本地部署:IoT DC3 是一个基于 Spring Cloud 的开源的、分布式的物联网(IoT)平台本地部署步骤 如有不了解可先参考我的另一篇文章本地部署: 1 环境准备: JDK 8 以上 docker 安装好 下载docker-compose-dev.yml 文件 执行基础环境docker安装 …

c++--运算符重载

1.重载的运算符 (1).重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。 (2).除了重载的函数调用运算符operator()之外&#xff0c;其他重载运算符不能含有默认实参。 (3).对一个重载的运算符&#xff0c;其优先级和结合律与对应的内置运算符保持一致。 (4).当一个…

STM32通用定时器

本文实践&#xff1a;实现通过TIM14_CH1输出PWM&#xff0c;外部显示为呼吸灯。 通用定时器简介 拥有TIM2~TIM5、TIM9~TIM14 一共10个定时器&#xff0c;具有4路独立通道&#xff0c;可用于输入捕获、输出比 较&#xff0c;同时包含了基本定时去的所有功能。 通用定时器的结…

音视频技术开发周刊 | 322

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 超级AI不会主宰人类&#xff0c;但人工智能必须开源&#xff01;LeCun最新采访引全网300万人围观 LeCun最新访谈视频中&#xff0c;再次坦露了自己对开源AI的看法。超级AI…

[MySQL--基础]多表查询

前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录&#xff1a;生活中最大的挑战就是发现自己是谁。然后&#xff0c;坚定不移地成为那个人。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法 MySQL数据库 多表查询 前言多表关系概述&#x1f…

怎么翻译英文医学文献资料

文献翻译是一项要求严谨、精确且地道的工作&#xff0c;对于医学文献翻译更是如此。那么&#xff0c;怎么翻译英文医学文献资料&#xff0c;医学英文文献翻译公司哪个好&#xff1f; 专业人士指出&#xff0c;在翻译医学文献时&#xff0c;理解原文的语境是至关重要的。这不仅需…

Vue JAVA开发常用模板

1.VsCode添加模板 左下角设置》用户代码片段 新建全局代码片段》将模板粘贴仅文件&#xff08;prefix用于指定触发关键字&#xff09; 添加成功过后输入配置的关键字即可使用 1.1 vue2模板 {// Example:"Print to console": {"prefix": "vue2",…

创建conan包-Understanding Packaging

创建conan包-Understanding Packaging 1 Understanding Packaging1.1 Creating and Testing Packages Manually1.2 Package Creation Process 本文是基于对conan官方文档Understanding Packaging翻译而来&#xff0c; 更详细的信息可以去查阅conan官方文档。 1 Understanding …

Leetcode刷题详解——等差数列划分

1. 题目链接&#xff1a;413. 等差数列划分 2. 题目描述&#xff1a; 如果一个数列 至少有三个元素 &#xff0c;并且任意两个相邻元素之差相同&#xff0c;则称该数列为等差数列。 例如&#xff0c;[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 给你一个整数数组 …

24、到底什么是感受野?

在之前的文章中介绍卷积算法时,一直在强调一个地方,那就是卷积算法是——卷积核在输入图像上滑动扫描的过程。 在每一次扫描时,可以把卷积核的在长宽方向的大小看做一个窗口,透过窗口可以看到的输入图像的范围,就称为感受野。 也就是神经网络(卷积)在每一次扫描过程中…

RHEL8.9 静默安装Oracle19C

RHEL8.9 静默安装Oracle19C 甘肃圆角网络科技开发有限公司 说明(GUI)&#xff1a;  1.实际业务场景中&#xff0c;Linux环境一般情况下是没有GUI的。没有GUI并不意味着没有安装图形界面。可能在部署Linux操作系统环境的时候安装了桌面环境&#xff0c;只是启动的时候设置了启动…