protobuf和gRPC

1|0一、grpc介绍

  • grpc 是 google 给出的 rpc 调用方式,它基于 google 的 protobuf 定义方式,提供了一整套数据定义和 rpc 传输的方式

  • 它是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHPC# 支持。这是grpc的github地址

img

1|11.1 RPC 介绍

  • 在介绍 grpc 之前有必要首先介绍一下 rpc。RPC 的英文全名是 Remote Procedure Call(远程过程调用),它实现了远程函数或方法的本地调用。由于不在一个内存空间,不能直接调用,因此需要通过网络来表达调用的语义和传达调用的数据。其基本流程如下图所示。

img

  • 客户端需要调用某个远程函数,首先需要在 client stub 进行函数语义和数据的网络表达,之后将转义好的数据通过 sockets 经过网络传到服务器端,之后同样经过 sever stub 的解析调用远程服务的函数。之后通过同样的链路将函数的结果返回给客户端

1|21.2 Protobuf 介绍

  • Protobuf 是 Google 给出的一种通用的数据表示方式,通过 proto 文件定义的数据格式,可以一键式的生成 C++,Python,Java 等各种语言实现

  • protobuf经历了protobuf2和protobuf3,pb3比pb2简化了很多,目前主流的版本是pb3

img

2|0二、下载生成proto工具

# 下载地址 http://github.com/protocolbuffers/protobuf/releases # 把可执行文件加入到环境变量 vim .bash_profile # proto PATH=$PATH:/Users/liuqingzheng/soft/protoc-21.0-rc-1-osx-x86_64/bin: ####注意:mac下如果下载的是protoc-21.0-rc-1-osx-x86_64,后面使用google内置的proto会找不到,建议下载 https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-osx-aarch_64.zip然后配置环境变量

image-20220514031404183

3|0三、下载go的依赖包

# 设置go proxy 代理,下载速度快 export GOPROXY=https://goproxy.io,direct # https://goproxy.io/zh/ ,设置成功可以同过go env查看一下 go env # 此方式弃用 go get github.com/golang/protobuf/protoc-gen-go # 使用该方式 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2 # 运行成功会在go的bin路径下生成一个可执行文件 /Users/liuqingzheng/go/bin/protoc-gen-go /Users/liuqingzheng/go/bin/protoc-gen-go-grpc # mac下的话,把该可执行文件复制到/usr/local/bin/路径() cp /Users/liuqingzheng/go/bin/protoc-gen-go /usr/local/bin/protoc-gen-go cp /Users/liuqingzheng/go/bin/protoc-gen-go-grpc /usr/local/bin/protoc-gen-go-grpc # Goland安装高亮Protocol 的插件 # Plugins中搜索 Protocol Buffers

4|0四、快速使用

4|14.1 编写proto

syntax = "proto3"; // 一定要加go的package路径,否则生成不了,意思是把go文件生成到proto文件夹下 // ;proto 表示生成的go文件,包名为proto option go_package = "../proto;proto"; // 类似于go的结构体,可以定义属性 message HelloRequest { string name = 1; // 1 是编号,不是值 // int32 age =2 }

4|24.2 生成go脚本

// 执行命令 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto // 执行完生成一个helloworld.pb.go 在这个go文件中,可以找到咱们定义的结构体

image-20220514041007425

4|34.3 编写main.go

package main import ( "encoding/json" "fmt" "go_test_learn/proto" ) import pb "github.com/golang/protobuf/proto" // 导入这个模块 func main() { // proto 格式编码解码 req:=proto.HelloRequest{Name: "lqz"} r,_:=pb.Marshal(&req) fmt.Println(string(r)) // 解码 req2:=proto.HelloRequest{Name: "lqz"} pb.Unmarshal(r,&req2) fmt.Println(req2) // json 格式编码解码 type HelloRequest struct{ Name string } h:=HelloRequest{Name: "lqz"} r1,_:=json.Marshal(&h) fmt.Println(string(r1)) h1:=HelloRequest{} json.Unmarshal(r1,&h1) fmt.Println(h1) }

4|44.4 修改proto,加入更多参数

syntax = "proto3"; // 一定要加go的package路径,否则生成不了,意思是把go文件生成到proto文件夹下 // ;proto 表示生成的go文件,包名为proto option go_package = "../proto;proto"; // 类似于go的结构体,可以定义属性 message HelloRequest { string name = 1; // 1 是编号,不是值 int32 age = 2; repeated string girls = 3; }

4|54.5 重新生成

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto

4|64.6 修改代码

package main import ( "encoding/json" "fmt" "go_test_learn/proto" ) import pb "github.com/golang/protobuf/proto" // 导入这个模块 func main() { // proto 格式编码解码 req := proto.HelloRequest{Name: "lqz", Age: 19, Girls: []string{"刘亦菲", "迪丽热巴"}} r, _ := pb.Marshal(&req) fmt.Println(string(r)) // 解码 req2 := proto.HelloRequest{} pb.Unmarshal(r, &req2) fmt.Println(req2) // json 格式编码解码 type HelloRequest struct { Name string Age int32 Girls []string } h := HelloRequest{Name: "lqz",Age: 19, Girls: []string{"刘亦菲", "迪丽热巴"}} r1, _ := json.Marshal(&h) fmt.Println(string(r1)) h1 := HelloRequest{} json.Unmarshal(r1, &h1) fmt.Println(h1) }

5|0五、完整的客户端服务端

5|15.1 proto文件

syntax = "proto3"; option go_package = ".;proto"; // 定义一个服务,gRPC自有的,它需要用grpc插件生成,也就是咱们安装的那个插件 service Hello{ // 服务内有一个函数叫Hello,接收HelloRequest类型参数,返回HelloResponse类型参数 rpc Hello(HelloRequest) returns(HelloResponse); } // 类似于go的结构体,可以定义属性 message HelloRequest { string name = 1; // 1 是编号,不是值 int32 age = 2; repeated string girls = 3; } // 定义一个响应的类型 message HelloResponse { string reply =1; }

5|25.2 生成go脚本

# protoc 可执行文件必须加入环境变量 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto

5|35.3 server.go

package main import ( "context" "fmt" "go_test_learn/proto" "google.golang.org/grpc" "net" ) // 定义一个结构体,名字随意,只要实现Hello方法即可,相当于proto的service type HelloServer struct { proto.UnimplementedHelloServer } // 给结构体绑定Hello方法 // 请求参数必须是两个:context context.Context, request *proto.HelloRequest // 返回参数必须是两个:*proto.HelloResponse, error // 自动生成的go文件中结构体中的字段给转成大写字母开头,符合go导出字段规范 func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) { fmt.Println(request.Name) return &proto.HelloResponse{Reply:"ok"}, nil } func main() { // 创建一个grpc对象 g := grpc.NewServer() s := HelloServer{} // 把s注册到g对象中 proto.RegisterHelloServer(g,&s) // 监听端口 lis,error:=net.Listen("tcp","0.0.0.0:50052") if error!=nil{ panic("启动服务异常") } g.Serve(lis) }

5|45.4 client.go

package main import ( "context" "fmt" "go_test_learn/proto" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func main() { // 连接grpc服务端,grpc.WithInsecure()跳过对服务器的验证 //conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithInsecure()) conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { panic("连接服务异常") } //defer 关闭 defer conn.Close() //创建一个客户端,传入连接对象 client := proto.NewHelloClient(conn) request := proto.HelloRequest{ Name: "lqz", } //调用SayHello方法,传入两个参数 res, err := client.Hello(context.Background(), &request) if err != nil { panic("调用方法异常") } //打印出返回的数据 fmt.Println(res.Reply) }

6|0六、注意

6|16.1 protoc语法

-I 或者 --proto_path:用于指定所编译的源码,就是我们所导入的proto文件,支持多次指定,按照顺序搜索,如果未指定,则使用当前工作目录。 --go_out:同样的也有其他语言的,例如--java_out、--csharp_out,用来指定语言的生成位置,用于生成*.pb.go 文件 --go_opt:paths=source_relative 指定--go_out生成文件是基于相对路径的 --go-grpc_out:用于生成 *_grpc.pb.go 文件 --go-grpc_opt: paths=source_relative 指定--go_grpc_out生成文件是基于相对路径的 require_unimplemented_servers=false 默认是true,会在server类多生成一个接口 --grpc-gateway_out:是使用到了 protoc-gen-grpc-gateway.exe 插件,用于生成pb.gw.go文件 --grpc-gateway_opt: logtostderr=true 记录log paths=source_relative 指定--grpc-gateway_out生成文件是基于相对路径的 generate_unbound_methods=true 如果proto文件没有写api接口信息,也会默认生成 --openapiv2_out:使用到了protoc-gen-openapiv2.exe 插件,用于生成swagger.json 文件

6|26.2 mustEmbedUnimplemented***方法问题

// 新版protoc-gen-go不支持grpc服务生成,需要通过protoc-gen-go-grpc生成grpc服务接口,但是生成的Server端接口中会出现一个mustEmbedUnimplemented***方法,是为了解决前向兼容问题,如果不解决,就无法传递给RegisterXXXService方法,解决办法有两个: 1. 在grpc server实现结构体中匿名嵌入Unimplemented***Server结构体 type HelloServer struct { proto.UnimplementedHelloServer } 2. 使用protoc生成server代码时命令行加上关闭选项protoc --go-grpc_out=require_unimplemented_servers=false //protoc --go_out=. --go_opt=paths=source_relative proto/helloworld.proto //protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false --go-grpc_opt=paths=source_relative proto/helloworld.proto

__EOF__

本文作者BigSun丶
本文链接https://www.cnblogs.com/Mcoming/p/18080561.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   BigSun丶  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示