首页 > 其他分享 >Golang gRPC概述及入门示例

Golang gRPC概述及入门示例

时间:2024-01-25 19:46:34浏览次数:27  
标签:pb err 示例 gRPC Send Golang grpc go

1、概述

1.1 什么是gRPC

RPC的全称是Remote Procedure Call,远程过程调用。RPC是一种协议,它实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样

gRPC又是什么呢?用官方的话来说:

A high-performance, open-source universal RPC framework

gRPC是一个高性能的、开源的通用的RPC框架。

gRPC中,我们称调用方为client,被调用方为server。 跟其他的RPC框架一样,gRPC也是基于”服务定义“的思想。简单的来讲,就是我们通过某种方式来描述一个服务,这种描述方式是语言无关的。在这个”服务定义“的过程中,我们描述了我们提供的服务的服务名是什么,有哪些方法可以被调用,这些方法有什么样的入参,有什么样的回参。

也就是说,在定义好了这些服务、这些方法之后,gRPC会屏蔽底层的细节,client只需要直接调用定义好的方法,就能拿到预期的返回结果。对于server端来说,还需要实现我们定义的方法。同样的,gRPC也会帮我们屏蔽底层的细节,我们只需要实现所定义的方法的具体逻辑即可。

你可以发现,在上面的描述过程中,所谓的”服务定义“,就跟定义接口的语义是很接近的。我更愿意理解为这是一种”约定“,双方约定好接口,然后server实现这个接口,client调用这个接口的代理对象。至于其他的细节,交给gRPC

此外,gRPC还是语言无关的。你可以用C++作为服务端,使用Golang、Java等作为客户端。为了实现这一点,我们在”定义服务“和在编码和解码的过程中,应该是做到语言无关的。

如下图所示就是一个典型的RPC结构图。

通过上图可以看到gRPC使用了Protocol Buffers。本文不会展开来讲Protocol Buffers(详细proto语法参见:Golang使用Protobuf,你可以把他当成一个代码生成工具以及序列化工具。这个工具可以把我们定义的方法,转换成特定语言的代码。比如你定义了一种类型的参数,他会帮你转换成Golang中的struct结构体,你定义的方法,他会帮你转换成func函数。此外,在发送请求和接受响应的时候,这个工具还会完成对应的编码和解码工作,将你即将发送的数据编码成gRPC能够传输的形式,又或者将即将接收到的数据解码为编程语言能够理解的数据格式。

1.2 使用场景

  1. 低延时、高可用的分布式系统;
  2. 移动端与云服务端的通讯;
  3. 使用protobuf,独立于语言的协议,支持多语言之间的通讯;
  4. 可以分层扩展,如:身份验证,负载均衡,日志记录,监控等;

1.3 gRPC 与 RESTful API比较

特性  gRPC  RESTful API
规范 必须.proto   可选 OpenAPI
协议  HTTP/2 任意版本的  HTTP 协议
有效载荷 Protobuf(小、二进制) JSON(大、易读)
浏览器支持  否(需要 grpc-web)
流传输 客户端、服务端、双向 客户端、服务端
代码生成  是 OpenAPI + 第三方工具

2、环境配置

2.1 安装配置protocol buffers和protoc-gen-go

步骤参见:Mac下安装配置Protocol Buffers 

2.2 获取gRPC

1 go get google.golang.org/grpc

这一步安装的是gRPC的核心库。

2.3 获取protoc-gen-go-grpc

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

安装protoc-gen-go-grpc用于.proto-->***_grpc.pb.go。

3、gRPC入门示例

在开始开发之前,先说说我们的目标。

在这个grpc-practice项目中,我希望实现一个功能,客户端可以发送消息给服务端,服务端收到消息后,返回响应给客户端。

项目结构如下:

注意: 这是整个项目所有文件生成完后的结构,所有.proto和.go文件都是在3.1及其后步骤生成的,go.mod内容如下:

+ View Code

3.1 定义服务

正如前面所说的,在开发serverclient之前,我们需要先定义服务。这里直接粘贴下示例proto文件内容。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 文件路径grpc-practice/pkg/proto/message.proto   syntax = "proto3"; // 这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中,../pb代表在当前目录的上一级pb目录中生成,message代表了生成的go文件的包名是message。 option go_package = "../pb;pb";   message MessageResponse {   string responseSomething = 1; }   message MessageRequest {   string saySomething = 1; }   service MessageSender {   rpc Send(MessageRequest) returns (MessageResponse) {} }

很容易可以看出,我们定义了一个service,称为MessageSender,这个服务中有一个rpc方法,名为Send。这个方法会发送一个MessageRequest,然后返回一个MessageResponse

接着在grpc-practice/pkg/proto目录下执行如下命令:

1 2 protoc --go_out=.  message.proto protoc --go-grpc_out=. message.proto

这两条命令会grpc-practice/pkg/pb目录中生成message.pb.go、message_grpc.pb.go这两个文件。在这两个文件中,包含了我们定义方法的go语言实现,也包含了我们定义的请求与相应的go语言实现。

简单来讲,就是protoc-gen-go已经把你定义的语言无关的message.proto转换为了go语言的代码,以便serverclient直接使用。

注意:在网上的一些教程中,有这样的生成方式:

1 protoc --go_out=plugins=grpc:. message.proto

这种生成方式,使用的就是github版本的protoc-gen-go,而目前这个项目已经由Google接管了。并且,如果使用这种生成方式的话,并不会生成上图中的xxx_grpc.pb.goxxx.pb.go两个文件,只会生成xxx.pb.go这种文件。

3.2 服务端

3.2.1 实现服务定义的方法

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 文件路径grpc-practice/pkg/serviceImpl/MessageSenderServerImpl.go   package serviceImpl   import (     "context"     "grpc-practice/pkg/pb"     "log" )   type MessageSenderServerImpl struct {     *pb.UnimplementedMessageSenderServer }   func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {     log.Println("receive message:", request.GetSaySomething())     resp := &pb.MessageResponse{}     resp.ResponseSomething = "roger that!"     return resp, nil }

很容易可以看出,MessageSenderServerImpl实现了MessageSenderServer接口,并实现定义。也就是说,这一部分是需要我们在Server端实现这个send方法的。

3.2.2 gRPC服务端注册定义的服务并监听

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 文件路径grpc-practice/pkg/service/main.go   package main   import (     "google.golang.org/grpc"     "grpc-practice/pkg/pb"     "grpc-practice/pkg/serviceImpl"     "log"     "net" )   func main() {     srv := grpc.NewServer()     pb.RegisterMessageSenderServer(srv,serviceImpl.MessageSenderServerImpl{})     listener, err := net.Listen("tcp"":8002")     if err != nil {         log.Fatalf("failed to listen: %v", err)     }       err = srv.Serve(listener)     if err != nil {         log.Fatalf("failed to serve: %v", err)     } }

很容易可以看出,我们在这一部分创建了一个grpcServer,然后注册了我们的Service,在注册函数的第二个参数中,我们传进去了一个MessageSenderServerImpl实例。

监听过程跟golang的web服务器是很像的,也是创建Handler,然后对端口进行监听,监听8002端口的TCP连接,然后启动服务器。

至此,服务端开发完毕。

3.3 客户端

在客户端中,我们应该先与server端建立连接,然后才能够调用各种方法。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 文件路径grpc-practice/pkg/client/main.go   package main   import (     "context"     "google.golang.org/grpc"     "grpc-practice/pkg/pb"     "log" )   func main() {     conn, err := grpc.Dial("127.0.0.1:8002",grpc.WithInsecure())     if err != nil {         log.Fatalf("did not connect: %v", err)     }     defer conn.Close()       client := pb.NewMessageSenderClient(conn)     resp, err := client.Send(context.Background(), &pb.MessageRequest{SaySomething: "hello world!"})     if err != nil {         log.Fatalf("could not greet: %v", err)     }     log.Println("receive message:", resp.GetResponseSomething()) }

以上代码,就是跟本地的8002端口建立连接。然后,本地创建了一个client,然后直接调用我们之前定义好的Send方法,就可以实现我们需要的逻辑了,调用server段的方法和调用本地方法一样方便。

server端和client端都跑起来,你会看到这样的画面:

 

 至此,gRPC示例成功。

4、gPRC 生成代码为什么会有 UnimplementedMessageSenderServer和 mustEmbedUnimplementedMessageSenderServer{}

这里先粘一下message_grpc.pb.go文件内容:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 # 文件路径grpc-practice/pkg/pb/message_grpc.pb.go   // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc             v3.20.1 // source: message.proto   package pb   import (     context "context"     grpc "google.golang.org/grpc"     codes "google.golang.org/grpc/codes"     status "google.golang.org/grpc/status" )   // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7   // MessageSenderClient is the client API for MessageSender service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type MessageSenderClient interface {     Send(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error) }   type messageSenderClient struct {     cc grpc.ClientConnInterface }   func NewMessageSenderClient(cc grpc.ClientConnInterface) MessageSenderClient {     return &messageSenderClient{cc} }   func (c *messageSenderClient) Send(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error) {     out := new(MessageResponse)     err := c.cc.Invoke(ctx, "/MessageSender/Send", in, out, opts...)     if err != nil {         return nil, err     }     return out, nil }   // MessageSenderServer is the server API for MessageSender service. // All implementations must embed UnimplementedMessageSenderServer // for forward compatibility type MessageSenderServer interface {     Send(context.Context, *MessageRequest) (*MessageResponse, error)     mustEmbedUnimplementedMessageSenderServer() }   // UnimplementedMessageSenderServer must be embedded to have forward compatible implementations. type UnimplementedMessageSenderServer struct { }   func (UnimplementedMessageSenderServer) Send(context.Context, *MessageRequest) (*MessageResponse, error) {     return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") } func (UnimplementedMessageSenderServer) mustEmbedUnimplementedMessageSenderServer() {}   // UnsafeMessageSenderServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to MessageSenderServer will // result in compilation errors. type UnsafeMessageSenderServer interface {     mustEmbedUnimplementedMessageSenderServer() }   func RegisterMessageSenderServer(s grpc.ServiceRegistrar, srv MessageSenderServer) {     s.RegisterService(&MessageSender_ServiceDesc, srv) }   func _MessageSender_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {     in := new(MessageRequest)     if err := dec(in); err != nil {         return nil, err     }     if interceptor == nil {         return srv.(MessageSenderServer).Send(ctx, in)     }     info := &grpc.UnaryServerInfo{         Server:     srv,         FullMethod: "/MessageSender/Send",     }     handler := func(ctx context.Context, req interface{}) (interface{}, error) {         return srv.(MessageSenderServer).Send(ctx, req.(*MessageRequest))     }     return interceptor(ctx, in, info, handler) }   // MessageSender_ServiceDesc is the grpc.ServiceDesc for MessageSender service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var MessageSender_ServiceDesc = grpc.ServiceDesc{     ServiceName: "MessageSender",     HandlerType: (*MessageSenderServer)(nil),     Methods: []grpc.MethodDesc{         {             MethodName: "Send",             Handler:    _MessageSender_Send_Handler,         },     },     Streams:  []grpc.StreamDesc{},     Metadata: "message.proto", }

 通过UnsafeMessageSenderServer接口注释可以这个是为了向前兼容,具体是怎么做呢?

其实是这样子: 

因为在 protoc 帮我们生成的 .pb.go 文件中定义了 UnimplementedXxxServer 结构体,并且 *UnimplementedXxxServer 实现了 XxxServer 这个接口。所以我们写一个 XxxServerImpl,嵌入 *UnimplementedXxxServer 类型,也就实现了 XxxServer 这个接口。

1 2 3 4 5 6 7 type MessageSenderServerImpl struct {     *pb.UnimplementedMessageSenderServer }   func (MessageSenderServerImpl) Send(context context.Context, request *pb.MessageRequest) (*pb.MessageResponse, error) {     ...... }

如果以后 .proto 协议中的 service 有变更,增加、删除函数或者修改原来的函数,重新生成 .pb.go 文件后,XxxServer 这个接口也相应的变化了,由于protoc 帮我们重新生成了 UnimplementedXxxServer,它一定是实现了 XxxServer 这个接口的,所以我们的 XxxServerImpl 也是实现了 XxxServer 这个接口的。只不过在调用我们重写的 rpc 方法时,调用的可能就是嵌入类型 *pb.UnimplementedMessageSenderServer它的 Send了。(这涉及到 golang 的嵌入和组合:现有一个 Struct ,嵌入了一个其他类型,用外部类型调用某方法,如果外部类型包含了符合要求的接口实现,它的方法将会被使用。否则,通过方法提升,内部类型的接口实现可以直接被外部类型使用。)

5、总结

简单的来讲,我们在*.proto文件中定义了方法,然后在server端实现定义的rpc方法的具体逻辑,在client端调用这个方法。

对于其他的部分,由proto buffer负责对Golang中存储的数据结构与rpc传输中的数据进行转换,grpc负责封装所有的逻辑。

参考:https://zhuanlan.zhihu.com/p/258879142

参考:https://blog.csdn.net/canon_in_d_major/article/details/108135724

标签:pb,err,示例,gRPC,Send,Golang,grpc,go
From: https://www.cnblogs.com/beatle-go/p/17988004

相关文章

  • Stream流操作示例
    1privatestaticdoubleoneMoney;2privatestaticdoubletwoMoney;3privatestaticdoublesumMoney;45publicstaticvoidmain(String[]args){6List<Employee>list1=newArrayList<>();7list1.add(new......
  • rust使用lazy_static对全局变量多线程并发读写示例
    首先需要在项目依赖Cargo.toml添加lazy_static依赖项[dependencies]lazy_static="1.4.0"示例代码如下:uselazy_static::lazy_static;usestd::sync::{RwLock,RwLockReadGuard,RwLockWriteGuard};usestd::thread;#[derive(Debug)]structSharedData{data:Vec<......
  • Python requests模块POST提交请求,不同Content-type对应的参数示例
    1.'content-type':'application/x-www-form-urlencoded'data参数提交文本或字典都可以headers为空时,data提交content-type默认也是application/x-www-form-urlencodedrequests.post(url,headers={'content-type':'application/x-www-form-urlencoded'}......
  • 用C++11打造智能观察者模式:详解实现步骤完整示例代码
     观察者模式是一种行为设计模式,其中一个对象(主题)维护其依赖对象(观察者)的列表,当主题的状态发生变化时,它通知所有观察者。以下是一个使用C++11实现观察者模式的简单例子:定义观察者接口(Observer): 创建一个观察者接口,该接口包含观察者需要实现的更新方法。这个接口可以包含其他......
  • std::function类的使用示例
    std::function是C++标准库中的一个模板类,用于封装可调用的目标,比如函数、函数指针、成员函数指针、Lambda表达式等,使得它们可以像普通函数一样被调用。这种灵活性使得std::function在许多场景下都非常有用。以下是std::function的一般用法:1.封装函数指针1.1不带参数和返回值......
  • 华为三层交换机与路由器配置上网示例——学会这个,你就是IT界大佬
    特性配置案例适用的产品和版本说明本手册适用于通过命令行配置的框式交换机和盒式交换机(S300、S500、S2700、S3700、S5700、S6700、S7700、S7900、S9700共用一套)的多个版本,每个案例所支持的产品和版本不同,每个案例适用产品和版本请参看具体页面中的“配置注意事项”。若无特殊说明,......
  • IPC-MQ-代码示例
    客户端-服务端版本一Codeintcommom_msg(intmsgFlag){ //生成IPC关键字 key_tk=ftok(PATHNAME,PROJ_ID); //获取消息队列ID intmsgId=msgget(k,msgFlag); if(msgId<0) { perror("msggeterror"); return-2; } returnmsgId;}intget_msg(void)......
  • 1.27号(本周六)直播:golang开发远程控制工具
    本次的课程的内容为:1、远控编写2、工具代码编写3、工具测试 1月27日晚20:00,我们不见不散~ Ms08067安全实验室专注于网络安全知识的普及和培训,是专业的“图书出版+培训”的网络安全在线教育平台,专注于网络安全领域中高端人才培养。平台已开设Web安全零基础就业,Web高级安全......
  • Optional类的使用示例
    Optional类是Java8中引入的一个新特性,它可以用来解决空指针异常的问题。在之前的版本中,如果一个方法返回的结果有可能为空,我们通常需要在使用该结果之前进行判空操作,以避免出现NullPointerException。而使用Optional类可以更加简洁和安全地处理这种情况。下面是一个使用Optional......
  • Golang map实现分析
    数据结构go的map采用数组+链表形式存储,数据存放于hmap中:typehmapstruct{countint//哈希表的元素个数,即len()flagsuint8//map状态Buint8//2^B为桶的数量noverflowuint16//溢出桶的数量(预估)hash0uint32//hashs......