Go语言grpc服务开发——Protocol Buffer

文章目录

    • 一、Protocol Buffer简介
    • 二、Protocol Buffer编译器安装
    • 三、proto3语言指南
    • 四、序列化与反序列化
    • 五、引入grpc-gateway
      • 1、插件安装
      • 2、定义proto文件
      • 3、生成go文件
      • 4、实现Service服务
      • 5、gRPC服务启动方法
      • 6、gateway服务启动方法
      • 7、main函数启动
      • 8、验证

相关参考链接:

protobuf官方文档

proto3语法指南

protoc下载地址

grpc-gateway官方文档

grpc-gateway readme

swagger安装文档

【当前代码Demo】

一、Protocol Buffer简介

作为一个后端开发,我们用的最多的传递信息的协议是http协议。性能需求的提高,要求我们转向性能更高的协议,这就不得不提到一个由Google公司推广的一个小而快的rpc协议——protocol buffer协议。它到底有着什么样的魔力?让大家觉得真香定律。

Protocol Buffer是一个由Google开发的协议,是一个可以对结构化数据的序列化和反序列化协议。谷歌开发它的目的是提供一种比XML更好的方式来使系统进行通信。因此,他们致力于使其比XML更简单、更小、更快、更易于维护。这个协议甚至超过了JSON,具有更好的性能、更好的可维护性和更小的体积。

另外protoc支持多语言,以及跨平台。

天然支持C、C++、Java、Python、PHP、Ruby、Kotlin等语言。
不支持Go、Dart等语言。所以基于Go语言需要额外安装插件,下面会说到。

  • Why Protocol Buffer?——简单来说它更小、因此也更快。举例如下:
// 当前代码位于 https://gitee.com/liuwen766/protobuf-demo.git
func TestSerialize(t *testing.T) {// 这个是基于proto生成的personFromProto := &pb.Person{Name:     "我是小明",Age:      18,PhoneNum: []string{"188", "120"},Pets: &pb.Pets{Type: "Cat",Name: "Tom",},}marshal1, _ := proto.Marshal(personFromProto)create, _ := os.Create(fileName)defer create.Close()n, err := create.Write(marshal1)if err != nil {log.Fatal("create.Write(marshal) has err:", err)return}log.Println("proto.Marshal——Serialize Success:", n)// 这个是Go结构体personFromStruct := &Person{Name:     "我是小明",Age:      18,PhoneNum: []string{"188", "120"},Pets: &Pets{Type: "Cat",Name: "Tom",},}marshal2, _ := json.Marshal(personFromStruct)create, _ = os.Create(fileName)defer create.Close()n, err = create.Write(marshal2)if err != nil {log.Fatal("create.Write(marshal) has err:", err)return}log.Println("json.Marshal——Serialize Success:", n)// 日志输出如下:【传递的Person信息一模一样,但是基于proto的长度只有38,它是二进制数据】//2024/01/22 18:13:16 proto.Marshal——Serialize Success: 38//2024/01/22 18:13:16 json.Marshal——Serialize Success: 107
}

二、Protocol Buffer编译器安装

编译proto文件前的环境准备工作只有简单的两个步骤:

  • step1、下载protoc 。 protoc下载地址
#将protoc.exe文件配置到环境变量,配置完之后验证
$ protoc --version
libprotoc 3.19.4
  • step2、安装go插件。

前面提到过protoc支持多语言,包括C、C++、Java、Python、PHP、Ruby、Kotlin等语言。
但是不支持Go、Dart等语言。所以基于Go语言需要额外安装插件。只需要下面一个简单的命令。

go get google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go

go get 命令会获取依赖包到go env的 GOMODCACHE 目录下。
go intall 命令会将对应的可执行文件安装到go env下的 GOPATH/bin 目录下。
因此需要配置GOPATH/bin为环境变量。否则会报错“‘protoc-gen-go’ 不是内部或外部命令,也不是可运行的程序或批处理文件。”

一个简单的proto3文件样例。

// 指定proto语言版本
syntax = "proto3";
// 生成*.pb.go文件的包路径
option go_package = "/pb";
// proto包路径 
package protobuf.demo;message Person{string name = 1;int32 age = 2;
}

通过protoc生成 *.pb.go 文件:

protoc -I . --go_out=./proto ./proto/person.proto
# 这里的三个 . 都表示当前目录

三、proto3语言指南

通过上面编写的简单proto文件,可以发现,proto文件中定义message与我们创建一个Go语言中的struct结构体类似。针对Go语言中的一些复杂类型,例如:数组[]int、集合map、枚举enum、嵌套等,proto自然也有相对应的定义。

  • repeated

repeated关键字的作用是用来定义数组,使用方式是repeated 数组类型 属性名称 = 字段编号;

message Person {repeated string name = 1;
}
  • map

map类型的定义方式是map <键类型,值类型> 属性名称 = 字段编号; ,这里需要注意对于map的键类型,只能定义为基本数据类型,但是值的类型可以是任何支持的类型。

message Person {map <string, Pets> pets =1;
}
// 嵌套
message Pets{string  Type = 1;string  name = 2;
}
  • enum

对于枚举的定义,需要用到enum关键字。

message Person{Sex sex = 5;
}
enum Sex{Sex_MAN = 0;SEX_WOMAN = 1;
}

一个完整示例。

// 指定proto语言版本
syntax = "proto3";
// 生成*.pb.go文件的包路径
option go_package = "/pb";
// proto包路径
package protobuf.demo;message Person{string name = 1;int32 age = 2;bool marry = 3;repeated string phoneNum = 4;map<string, string> address = 5;Sex sex = 6;Pets pets = 7;
}message Pets{string  Type = 1;string  name = 2;
}enum Sex{Sex_MAN = 0;SEX_WOMAN = 1;
}

定义完proto文件后,通过protoc生成 *.pb.go 文件,执行如下命令:

protoc -I . --go_out=./proto ./proto/person.proto
# 这里的三个 . 都表示当前目录

四、序列化与反序列化

以序列化和反序列化为例,演示如何使用由proto编译生成的*.pb.go文件

package protoimport ("encoding/json""log""os""testing""google.golang.org/protobuf/proto""protobuf-demo/proto/pb"
)var fileName1 = "person-proto.txt"
var fileName2 = "person-json.txt"type Pets struct {Type stringName string
}type Person struct {Name     stringAge      int32PhoneNum []stringAddress  map[string]stringPets     *Pets
}// 序列化
func TestSerialize(t *testing.T) {personFromProto := &pb.Person{Name:     "我是小明",Age:      18,PhoneNum: []string{"188", "120"},//Sex:      pb.Sex_Sex_MAN,Pets: &pb.Pets{Type: "Cat",Name: "Tom",},}marshal1, _ := proto.Marshal(personFromProto)create, _ := os.Create(fileName1)defer create.Close()n, err := create.Write(marshal1)if err != nil {log.Fatal("create.Write(marshal) has err:", err)return}log.Println("proto.Marshal——Serialize Success:", n)// why proto?let's look look json.personFromStruct := &Person{Name:     "我是小明",Age:      18,PhoneNum: []string{"188", "120"},//Sex:      pb.Sex_Sex_MAN,Pets: &Pets{Type: "Cat",Name: "Tom",},}marshal2, _ := json.Marshal(personFromStruct)create, _ = os.Create(fileName2)defer create.Close()n, err = create.Write(marshal2)if err != nil {log.Fatal("create.Write(marshal) has err:", err)return}log.Println("json.Marshal——Serialize Success:", n)// 日志输出如下:所以说proto更小//2024/01/25 18:13:16 proto.Marshal——Serialize Success: 38//2024/01/25 18:13:16 json.Marshal——Serialize Success: 107
}// 反序列化
func TestDeserialize(t *testing.T) {read, _ := os.ReadFile(fileName1)person := pb.Person{}err := proto.Unmarshal(read, &person)if err != nil {log.Fatal("proto.Unmarshal(read, &person) has err:", err)return}log.Printf("Deserialize Success: %+v", person)
}

五、引入grpc-gateway

grpc-gateway是protoc的一个插件,它会读取proto文件中的grpc服务的定义并将其生成RestFul JSON API,并将其反向代理到我们的grpc服务中。

在这里插入图片描述

grpc-gatway是在grpc上做的一个拓展。但是grpc并不能很好的支持客户端,以及传统的RESTful API。因此grpc-gateway诞生了,该项目可以为grpc服务提供HTTP+JSON接口。

1、插件安装

首先,在项目中去创建一个tools的文件,然后执行go mod tidy

//go:build tools
// +build toolspackage toolsimport (_ "github.com/envoyproxy/protoc-gen-validate"_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)

通过执行go install将这些可执行文件安装在GOPATH/bin目录下。

go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
go install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

2、定义proto文件

// 指定proto语言版本
syntax = "proto3";
// 生成*.pb.go文件的包路径
option go_package = "/pb";package protobuf.demo;// 导入api注释依赖【注意将这里的注释依赖包放在当前项目的根目录下】
import "google/api/annotations.proto";message Book {int32 id = 1;string name = 2;
}message CreateBookRequest {string name = 1;
}message CreateBookResponse{string code = 1;string message = 2;Book data = 3;
}// 定义服务
service BookService {// 创建Bookrpc CreateBook (CreateBookRequest) returns (CreateBookResponse) {option (google.api.http) = {// POST  /v1/bookspost: "/v1/books"body: "*"};};
}

注意这里需要导入api注释依赖。

由于项目依赖了google的proto文件,所以在使用protoc生成go文件的时候,需要将依赖的proto文件复制到项目中,依赖的proto文件仓库 google/api 和 google/protobuf 。下载下来,放在当前项目中的根目录下。注释依赖一般可以自动检测,不行就手动配置依赖。

3、生成go文件

执行命令

protoc -I . --grpc-gateway_out=./proto/gen --grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative --go_out=./proto/gen --go_opt paths=source_relative --go-grpc_out=./proto/gen --go-grpc_opt paths=source_relative ./proto/book.proto
# 如果报错没有目录,则手动创建目录

不要被这么长的命令唬住了,记住 . 表示当前目录就行。
几个命令分别对应前面安装的三个插件:protoc-gen-go、protoc-gen-go-grpc和protoc-gen-grpc-gateway
protoc -I .
–grpc-gateway_out=./proto/gen
–grpc-gateway_opt logtostderr=true --grpc-gateway_opt paths=source_relative
–go_out=./proto/gen
–go_opt paths=source_relative
–go-grpc_out=./proto/gen
–go-grpc_opt paths=source_relative
./proto/book.proto

4、实现Service服务

package serviceimport ("context""log""protobuf-demo/db"pb "protobuf-demo/proto/gen/proto"
)type BookService struct {// 这里是要实现的服务pb.UnimplementedBookServiceServer
}// 这里是实现的方法
func (b *BookService) CreateBook(ctx context.Context, req *pb.CreateBookRequest) (*pb.CreateBookResponse, error) {resp := &pb.CreateBookResponse{}db.Db.Mux.Lock()defer db.Db.Mux.Unlock()id := db.Db.GetId()book := pb.Book{Name: req.GetName(),Id:   id,}err := db.Db.Save(&book)if err != nil {return resp, err}resp.Data = &booklog.Printf("user %s create a book, %+v", db.GetUserId(ctx), &book)return resp, nil
}

5、gRPC服务启动方法

package grpcimport ("log""net""protobuf-demo/config""protobuf-demo/grpc/middle"pb "protobuf-demo/proto/gen/proto""protobuf-demo/service""google.golang.org/grpc"
)// Run grpc的启动方法
func Run() error {//Step1:监听端口,用于提供grpc服务grpcAddr := config.GetRpcAddr()listen, err := net.Listen("tcp", grpcAddr)if err != nil {log.Fatalf("tcp listen failed: %v", err)return err}defer listen.Close()// Step2:可以为这个grpc服务加一些定制化的特性option := []grpc.ServerOption{// 这里可以加一些middleware中间件grpc.UnaryInterceptor(middle.AuthInterceptor),}// Step3:创建一个grpc服务,它是空的服务,还不能接收/处理任何请求server := grpc.NewServer(option...)// Step4:进行服务注册registerServer(server)log.Printf("Serving gRPC on %s", listen.Addr())// Step5:grpc服务接收从监听端口过来的流量请求,对外提供服务return server.Serve(listen)
}func registerServer(server *grpc.Server) {// 注册 BookService 服务pb.RegisterBookServiceServer(server, service.NewBookService())
}

6、gateway服务启动方法

package gatewayimport ("context""log""net/http""protobuf-demo/config""protobuf-demo/gateway/middle""protobuf-demo/handler"pb "protobuf-demo/proto/gen/proto""github.com/grpc-ecosystem/grpc-gateway/v2/runtime""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)// Run gateway的启动方法
func Run() error {// Step1:创建一个客户端,连接grpc服务ctx := context.Background()option := []grpc.DialOption{// 这里可以加一些middleware中间件grpc.WithChainUnaryInterceptor(middle.AuthInterceptor),grpc.WithTransportCredentials(insecure.NewCredentials()),}// 创建grpc连接  连接到127.0.0.1:8001conn, err := grpc.DialContext(ctx, config.GetRpcAddr(), option...)if err != nil {log.Fatalf("dial failed: %v", err)return err}// Step2:创建一个ServeMux,它是 grpc-gateway 的请求多路复用器。serveMuxOption := []runtime.ServeMuxOption{// 响应拦截runtime.WithForwardResponseOption(middle.Interceptor),// 错误页自定义runtime.WithRoutingErrorHandler(middle.RoutingErrorHandler),// 自定义保留哪些请求头信息到整个上下文中runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {if s == "User-Id" {return s, true}return runtime.DefaultHeaderMatcher(s)}),}mux := runtime.NewServeMux(serveMuxOption...)// 自定义一些不好用proto编译的接口,比如这里的上传/下载接口{if err = mux.HandlePath(http.MethodPost, "/v1/objects", handler.Upload); err != nil {return err}if err = mux.HandlePath(http.MethodGet, "/v1/objects/{name}", handler.Download); err != nil {return err}}// Step3:将http路由注册到前面创建的ServeMux,通过grpc-gateway反向代理,从而提供http服务err = newGateway(ctx, conn, mux)if err != nil {log.Fatalf("register handler failed: %v", err)return err}// Step4:创建一个http服务server := http.Server{Addr: config.GetHttpAddr(), // 127.0.0.1:8002// http服务需要处理的ServeMuxHandler: mux,}log.Printf("Serving Http on %s", server.Addr)// Step5:进行监听并提供服务err = server.ListenAndServe()if err != nil {return err}return nil
}func newGateway(ctx context.Context, conn *grpc.ClientConn, mux *runtime.ServeMux) error {// 注册 BookService 服务,进行反向代理err := pb.RegisterBookServiceHandler(ctx, mux, conn)if err != nil {return err}return nil
}

7、main函数启动

package mainimport ("log""os""protobuf-demo/gateway""protobuf-demo/grpc"
)func main() {// 启动grpc服务go func() {err := grpc.Run()if err != nil {log.Fatal(os.Stderr, err)os.Exit(1)}}()//	启动gateway服务err := gateway.Run()log.Fatal(os.Stderr, err)os.Exit(1)
}

8、验证

启动服务之后,进行接口验证。

# 新增接口
curl --request POST \--url http://localhost:8002/v1/books \--header 'X-User-Id: ' \--header 'content-type: application/json' \--data '{"name": "三国演义"
}'

接下来,完成所有的增删改查接口。如下:

# 查询接口
curl --request GET \--url http://localhost:8002/v1/books/1 \--header 'content-type: application/json'# 更新接口
curl --request POST \--url http://localhost:8002/v1/books/1 \--header 'content-type: application/json' \--data '{"id": 1,"name": "三国演义2"
}'# 删除接口
curl --request DELETE \--url http://localhost:8002/v1/books/1 \--header 'content-type: application/json'

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

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

相关文章

msvcp140.dll丢失的解决方法

当你在尝试打开一个需要msvcp140.dll文件支持的程序或游戏时&#xff0c;如果系统提示该文件丢失&#xff0c;那么意味着在执行过程中缺少了这个关键的DLL文件。当系统无法找到或加载msvcp140.dll文件时&#xff0c;相应的程序将无法正常启动&#xff0c;这对于用户来说可能会非…

上位机图像处理和嵌入式模块部署(python opencv)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们谈到了qt&#xff0c;谈到了opencv&#xff0c;也谈到了嵌入式&#xff0c;但是没有说明python在这个过程当中应该扮演什么样的角色。open…

mac滚动截图

参考博客 https://www.zhihu.com/question/313673726/answer/2938671835 首先去AppStore搜索 iShot 这个也是要钱的&#xff0c;不过我输入appleID后&#xff0c;并没有扣我钱&#xff0c;不知道设么回事 然后打开iShot 点击观看视频&#xff0c;然后mac会自动打开一个新的…

JVM-字节码应用

一、字节码的应用远超你的想象 二、ASM介绍与读取字节码实战 用CoreAPI解析和TreeAPI都能做字节码解析&#xff0c;区别&#xff0c;TreeAPI必须读取完整字节码信息&#xff0c;才能做解析。 下面代码&#xff0c;使用CoreAPI做解析&#xff1a; package asm;public class MyM…

拦截器的简单使用

拦截器的简单使用 拦截器的使用创建拦截器preHandle 目标方法执行前执行postHandle 目标方法执行后执行afterCompletion 视图渲染后执行 拦截器使用场景返回值注册拦截器运用拦截器 拦截器的使用 创建拦截器 首先,我们需要创建一个拦截器器的类,并且需要继承自HandlerIntercep…

DEM高程地形瓦片数据Cesium使用教程

一、简介 从开始写文章到现在&#xff0c;陆续发布了全球90m、30m(包括哥白尼及ALOS)、12.5m全球级瓦片数据&#xff0c;以及中国12.5、日本10m、新西兰8m、等国家级瓦片数据&#xff0c;同时也发布了台湾20m、中国34省区12.5m等地区级瓦片数据。在数据发布的文章中对数据如何…

docker之部署青龙面板

青龙面板是一个用于管理和监控 Linux 服务器的工具&#xff0c;具有定时运行脚本任务的功能。在实际情况下也可以用于一些定期自动签到等任务脚本的运行。 本次记录下简单的安装与使用&#xff0c;请提前安装好docker&#xff0c;参考之前的文章。 一、安装部署 1、拉取镜像 # …

力扣每日一题 ---- 1039. 多边形三角剖分的最低得分

这题的难点在哪部分呢&#xff0c;其实是怎么思考。这道题如果之前没做过类似的话&#xff0c;还是很难看出一些性质的&#xff0c;这题原本的话是没有图片把用例显示的这么详细的。这题中有个很隐晦的点没有说出来 剖出来的三角形是否有交叉&#xff0c;这题中如果加一个三角…

Autodesk AutoCAD 2024:开启无限创意,塑造未来设计

随着科技的飞速发展&#xff0c;设计行业正在经历前所未有的变革。作为设计领域的核心软件&#xff0c;Autodesk AutoCAD 2024以其强大的功能和卓越的性能&#xff0c;引领着设计行业的创新潮流。 AutoCAD 2024不仅继承了前代版本的优点&#xff0c;更在功能和性能上进行了全面…

上门预约回收小程序开发,开启互联网回收模式

随着环保理念的提升&#xff0c;废弃物品不再采用丢弃的方式&#xff0c;更多的是进行回收&#xff0c;因此&#xff0c;我国回收行业迎来了发展蓝海。而废品回收行业在众多行业中也是一个创业蓝海选择&#xff0c;回收行业的市场体量庞大&#xff0c;有较大的发展前景。 如今…

插槽(64-67)

文章目录 插槽1.插槽 - 默认插槽(组件内可以定制一处结构)2.插槽 - 后备内容&#xff08;默认值&#xff09;3.插槽 - 具名插槽(组件内可以定制多处结构)4.作用域插槽(插槽的一个传参语法) 插槽 插槽分类:默认插槽和具名插槽 1.插槽 - 默认插槽(组件内可以定制一处结构) 作用…

微信小程序(十四)分包和分包预加载

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.分包的配置 2.分包预加载的写法 先说说为什么需要分包&#xff1a; 小程序追求小而快&#xff0c;主包的大小控制是小程序上线的硬性要求&#xff0c;分包有利于小程序优化加载速度 分包的注意事项&#xff1a…