• GRPC入门实战


    相关链接:
    gRPC官网:https://grpc.io/

    RPC框架一般用于后端服务数据通信比较大很频繁的场景。通信少时RPC和HTTP都可以用,怎么简单怎么来,满足需要就可。GRPC面向移动应用,当然它是通用的,后端也可以用,常用于分布式项目中,比较强大,支持双向通信。

    先来个简单的rpc示例代码,再来个简单的grpc示例代码。

    一、RPC

    RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上,也可以在不同的机器上。程序员使用时,就像调用本地程序一样,无需关注内部的实现细节。

    RPC 即远程过程调用,很简单的概念,就像调用本地服务(方法)一样调用服务器的服务(方法) 。

    • Go支持RPC

    在Go中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。net/rpc包允许 RPC客户端 程序通过网络或者其他IO连接调用一个远程对象的公开方法(该方法必须是外部可访问即首字母大写),在RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。

    1、GO语言实现rpc服务端

    package main
    
    import (
    	"errors"
    	"fmt"
    	"log"
    	"net/http"
    	"net/rpc"
    )
    
    func main() {
    	fmt.Println("rpc服务端启动......")
    	arith := new(Arith)
    	rpc.Register(arith)
    	rpc.HandleHTTP()
    	if err := http.ListenAndServe(":1234", nil); err != nil {
    		log.Fatal("serve error:", err)
    	}
    }
    
    type Args struct {
    	A, B int
    }
    type Quotient struct {
    	Quo, Rem int
    }
    
    type Arith int
    
    func (t *Arith) Multiply(args *Args, reply *int) error {
    	*reply = args.A * args.B
    	return nil
    }
    
    func (t *Arith) Divide(args *Args, quo *Quotient) error {
    	if args.B == 0 {
    		return errors.New("divide by 0")
    	}
    	quo.Quo = args.A / args.B
    	quo.Rem = args.A % args.B
    	return nil
    }
    
    • 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

    2、Go语言实现rpc客户端

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/rpc"
    )
    
    func main() {
    	fmt.Println("rpc客户端启动......")
    	client, err := rpc.DialHTTP("tcp", ":1234")
    	if err != nil {
    		log.Fatal("dialing:", err)
    	}
    	args := &Args{7, 8}
    	var reply int
    	err = client.Call("Arith.Multiply", args, &reply)
    	if err != nil {
    		log.Fatal("Multiply error:", err)
    	}
    	fmt.Printf("Multiply: %d*%d=%d\n", args.A, args.B, reply)
    
    	args = &Args{15, 6}
    	var quo Quotient
    	err = client.Call("Arith.Divide", args, &quo)
    	if err != nil {
    		log.Fatal("Divide error:", err)
    	}
    	fmt.Printf("Divide: %d/%d=%d...%d\n", args.A, args.B, quo.Quo, quo.Rem)
    }
    
    type Args struct {
    	A, B int
    }
    
    type Quotient struct {
    	Quo, Rem int
    }
    
    • 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

    二、GRPC

    gRPC是谷歌开源的一个高性能、开源、通用的RPC框架。基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议Protocol Buffers基本语法,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库,面向移动和 HTTP2 设计。【基于HTTP2】

    在服务端,服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。
    在客户端,客户端有一个存根(在某些语言中仅称为客户端),它提供与服务器相同的方法。

    gRPC 客户端和服务器可以在各种环境中运行和相互通信,并且可以用任何 gRPC 支持的语言编写【主流语言都支持】

    在这里插入图片描述

    1、protobuf

    ProtoBuf 【高效的二进制协议】具有强大的IDL(interface description language,接口描述语言)和相关工具集(主要是protoc)。用户写好.proto描述文件后,protoc可以将其编译成众多语言的接口代码。

    • 数据交互格式: protobuf
    • 通信方式: 最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现
    • 核心库: 在HTTP/2协议之上又构建了针对Go语言的gRPC核心库
    • Stub: 应用程序Application 通过 gRPC插件 生产的 Stub代码 和 gRPC核心库 通信,也可以直接和gRPC核心库通信。
      在这里插入图片描述

    2、gRPC 支持 4 类服务方法

    a、简单 RPC(Simple RPC)

    客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。即标准RPC通信。

    rpc SayHello(HelloRequest) returns (HelloResponse){}
    
    • 1

    b、服务端数据流式 RPC (Server-side streaming RPC)

    客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
    
    • 1

    c、客户端数据流式 RPC(Client-side streaming RPC)

    客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。典型的例子是物联网终端向服务器报送数据。

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}
    
    • 1

    d、双向数据流式 RPC(Bidirectional streaming RPC)

    两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。典型的例子就是聊天应用。

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}
    
    • 1

    3、准备工作

    a、安装go

    下载安装包:https://golang.google.cn/dl/

    解压安装包到安装目录,配置安装目录到环境变量【这一步如果不会,去百度】

    验证:

    C:\Users\DeskTop>go version
    go version go1.17 windows/amd64
    
    • 1
    • 2

    b、安装 protobuf

    protobuf 是一个跨平台和跨语言的数据结构存储和传输的便利工具。

    官方文档地址:https://developers.google.com/protocol-buffers/
    GitHub 地址:https://github.com/protocolbuffers/protobuf
    Releases 下载地址:https://github.com/protocolbuffers/protobuf/releases

    解压安装包到安装目录,配置安装目录到环境变量【这一步如果不会,去百度】

    验证:

    C:\Users\DeskTop>protoc --version
    libprotoc 3.17.3
    # Ensure compiler version is 3+
    
    • 1
    • 2
    • 3

    c、安装 Go protobuf 插件

    go get -u github.com/golang/protobuf/proto
    go get -u github.com/golang/protobuf/protoc-gen-go
    
    • 1
    • 2

    d、安装 grpc-go

    go get -u google.golang.org/grpc
    
    • 1
    • 编写服务端 .proto 文件
    • 生成服务端 .pb.go 文件并同步给客户端
    • 编写服务端提供接口的代码
    • 编写客户端调用接口的代码

    目录结构

    ├─ hello  -- 代码根目录
    │  ├─ go_client
    │     ├── main.go
    │     ├── proto
    │         ├── hello
    │            ├── hello.pb.go
    │  ├─ go_server
    │     ├── main.go
    │     ├── controller
    │         ├── hello_controller
    │            ├── hello_server.go
    │     ├── proto
    │         ├── hello
    │            ├── hello.pb.go
    │            ├── hello.proto
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这样创建目录是为了 go_client 和 go_server 后期可以拆成两个项目。

    4、Go 实现 gRPC 的服务端

    编写服务端 hello.proto 文件

    syntax = "proto3"; // 指定 proto 版本
    
    package hello;     // 指定包名
    option go_package = "./;hello";  // 指定生成 .pb.go文件的包名
    // 定义 Hello 服务
    service Hello {
    
    // 定义 SayHello 方法
    rpc SayHello(HelloRequest) returns (HelloResponse) {}
    
    // 定义 LotsOfReplies 方法
    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
    }
    
    // HelloRequest 请求结构
    message HelloRequest {
    string name = 1;
    }
    
    // HelloResponse 响应结构
    message HelloResponse {
    string message = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    了解更多 Protobuf 语法,请查看:

    https://developers.google.com/protocol-buffers/

    生成服务端 .pb.go

    protoc -I . --go_out=plugins=grpc:. ./hello.proto
    # 该命令在hello.proto文件所在目录下执行,会生成hello.pb.go文件
    
    • 1
    • 2

    同时将生成的 hello.pb.go 复制到客户端一份。

    查看更多命令参数,执行 protoc,查看 OPTION。

    • 编写服务端提供接口的代码
    // hello_server.go
    package hello_controller
    
    import (
    	"fmt"
    	"golang.org/x/net/context"
    	"hello/go_server/proto/hello"
    )
    
    type HelloController struct{}
    
    func (h *HelloController) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
    	return &hello.HelloResponse{Message : fmt.Sprintf("%s", in.Name)}, nil
    }
    
    func (h *HelloController) LotsOfReplies(in *hello.HelloRequest, stream hello.Hello_LotsOfRepliesServer)  error {
    	for i := 0; i < 10; i++ {
    		stream.Send(&hello.HelloResponse{Message : fmt.Sprintf("%s %s %d", in.Name, "Reply", i)})
    	}
    	return nil
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 服务端启动函数
    // main.go
    package main
    
    import (
    	"log"
    	"net"
    	"hello/go_server/proto/hello"
    	"hello/go_server/controller/hello_controller"
    	"google.golang.org/grpc"
    )
    
    const (
    	Address = "0.0.0.0:9090"
    )
    
    func main() {
    	listen, err := net.Listen("tcp", Address)
    	if err != nil {
    		log.Fatalf("Failed to listen: %v", err)
    	}
    
    	s := grpc.NewServer()
    
    	// 服务注册
    	hello.RegisterHelloServer(s, &hello_controller.HelloController{})
    
    	log.Println("Listen on " + Address)
    
    	if err := s.Serve(listen); err != nil {
    		log.Fatalf("Failed to serve: %v", err)
    	}
    }
    
    • 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

    运行:

    go run main.go
    
    2019/07/28 17:51:20 Listen on 0.0.0.0:9090
    
    • 1
    • 2
    • 3

    5、Go 实现 gRPC 的客户端

    • 编写客户端请求接口的代码
    package main
    
    import (
    	"hello/go_client/proto/hello"
    	"io"
    	"log"
    	"golang.org/x/net/context"
    	"google.golang.org/grpc"
    )
    
    const (
    	// gRPC 服务地址
    	Address = "0.0.0.0:9090"
    )
    
    func main() {
    	conn, err := grpc.Dial(Address, grpc.WithInsecure())
    	if err != nil {
    		log.Fatalln(err)
    	}
    	defer conn.Close()
    
    	// 初始化客户端
    	c := hello.NewHelloClient(conn)
    
    	// 调用 SayHello 方法
    	res, err := c.SayHello(context.Background(), &hello.HelloRequest{Name: "Hello World"})
    
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	log.Println(res.Message)
    
    	// 调用 LotsOfReplies 方法
    	stream, err := c.LotsOfReplies(context.Background(),&hello.HelloRequest{Name: "Hello World"})
    	if err != nil {
    		log.Fatalln(err)
    	}
    
    	for {
    		res, err := stream.Recv()
    		if err == io.EOF {
    			break
    		}
    
    		if err != nil {
    			log.Printf("stream.Recv: %v", err)
    		}
    
    		log.Printf("%s", res.Message)
    	}
    }
    
    • 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

    运行:

    go run main.go
    
    2019/07/28 17:58:13 Hello World
    2019/07/28 17:58:13 Hello World Reply 0
    2019/07/28 17:58:13 Hello World Reply 1
    2019/07/28 17:58:13 Hello World Reply 2
    2019/07/28 17:58:13 Hello World Reply 3
    2019/07/28 17:58:13 Hello World Reply 4
    2019/07/28 17:58:13 Hello World Reply 5
    2019/07/28 17:58:13 Hello World Reply 6
    2019/07/28 17:58:13 Hello World Reply 7
    2019/07/28 17:58:13 Hello World Reply 8
    2019/07/28 17:58:13 Hello World Reply 9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    浅浅的 C++ 11
    谷歌成功利用一台 54 量子比特的量子计算机
    VUE3页面截取部署后的二级目录地址
    基于大数据的安防体系建设研究和实践
    Img-Diff: 多模态大型语言模型的对比数据合成
    工业级开源facechain人物写真sd-webui插件使用方式
    客路旅行(KLOOK)面试(部分)(未完全解析)
    vue3笔记2
    总结html5中常见的选择器
    『吴秋霖赠书活动 | 第三期』《Python asyncio并发编程》
  • 原文地址:https://blog.csdn.net/qq_41822345/article/details/126320674