gRpc

作者: jason24L | 来源:发表于2022-04-08 11:48 被阅读0次

    gRPC(google+remote process call) 详解

    grpc 简介

    grpc是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的RPC结构图。
    [图片上传失败...(image-861e7e-1649389684397)]

    1. 客户端使用RPC调用远程方法,客户端的存根发起请求,对请求的数据使用protobuf对象序列化和压缩。
    2. 服务器接收到请求后,解码请求数据,使用protobuf反序列化为内存对象,处理业务逻辑并返回响应结果。
    3. 客户端收到服务端响应,解码响应数据,唤醒阻塞客户端。

    grpc 使用

    工欲善其事必先利其器,开发一个grpc示例之前先安装好需要的工具和插件

    grpc 示例

    1. 创建proto文件
    syntax = "proto3";
    package pb;
    
    option go_package = "./;pb";
    
    message Message {
      string body = 1;
    }
    
    message ReqMsgNum{
    }
    
    message RespMsgNum{
      int32 num = 1;
    }
    
    message ReqMatchWord{
      string word = 1;
    }
    
    message RespMatchWord{
      bool isMatch = 1;
    }
    
    service ChatService {
      rpc SayHello(Message) returns (Message) {}
      rpc GetMsgNum(ReqMsgNum) returns (stream RespMsgNum){}
      rpc MatchWord(stream ReqMatchWord) returns (RespMatchWord){}
      rpc Chat(stream Message) returns (stream Message){}
    }
    

    这里定义了四个方法
    sayHello 普通的rpc调用
    GetMsgNum 服务端流式调用,客户端普通的rpc调用
    MatchWord 客户端流式调用,服务端普通rcp调用
    Chat 双向流式调用

    1. 生成对应的go文件
    protoc --go_out=plugins=grpc:. chat.proto
    
    1. 服务端代码
    package main
    
    import (
        "google.golang.org/grpc"
        "log"
        "mytest/grpc/pb"
        "mytest/grpc/server/chat"
        "net"
    )
    
    func main() {
        listen, err := net.Listen("tcp", ":1234")
        if err != nil {
            log.Fatal(err)
        }
    
        s := chat.ChatServer{}
        rpcServer := grpc.NewServer()
        pb.RegisterChatServiceServer(rpcServer, &s)
    
        log.Println("start server")
        if err := rpcServer.Serve(listen); err != nil{
            log.Fatal(err)
        }
    }
    
    
    package chat
    
    import (
        "context"
        "log"
        "mytest/grpc/pb"
        "strings"
        "time"
    )
    
    type ChatServer struct {
    }
    
    // 普通请求
    func (s *ChatServer) SayHello(ctx context.Context, in *pb.Message) (*pb.Message, error) {
        log.Println("Receive message => ", in.Body)
        return &pb.Message{Body: "Hello From the Server!"}, nil
    }
    
    // 服务端流推送消息数量
    func (s *ChatServer) GetMsgTotal(in *pb.ReqMsgNum, stream pb.ChatService_GetMsgNumServer) error {
        var msgAmount int32 = 0
    
        for {
            msgAmount++
            err := stream.Send(&pb.RespMsgNum{Num: msgAmount})
            if err != nil {
                log.Println(err)
                break
            }
            time.Sleep(time.Second)
        }
    
    
        return nil
    }
    
    // 服务端接收客户端流
    func (s *ChatServer) MatchWord(stream pb.ChatService_MatchWordServer) error {
        var matchAmount int32
        for  {
            resp, err := stream.Recv()
            if err != nil {
                log.Println(err)
                break
            }
    
            log.Println("recv word => ", resp.Word)
            // TODO 业务处理 是否匹配单词
            if strings.Contains(resp.Word, "good") {
                matchAmount++
            }
        }
    
        err := stream.SendAndClose(&pb.RespMatchWord{IsMatch: matchAmount>0})
        if err != nil{
            log.Println(err)
            return err
        }
    
        return nil
    }
    
    // 双向流通信
    func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error {
        var msg string
        for  {
            resp, err := stream.Recv()
            if err != nil{
                log.Println(err)
                break
            }
            log.Println("recv msg => ", resp.Body)
    
            msg = "server " + resp.Body
            err = stream.Send(&pb.Message{Body: msg})
            if err != nil{
                log.Println(err)
                break
            }
            log.Println("send msg => ", msg)
        }
    
        return nil
    }
    
    
    1. 客户端代码
    package main
    
    import (
        "context"
        "fmt"
        "google.golang.org/grpc"
        "log"
        "mytest/grpc/pb"
        "time"
    )
    
    var ctx = context.Background()
    
    func main() {
        conn, err := grpc.Dial(":1234", grpc.WithInsecure())
        if err != nil{
            log.Fatal(err)
        }
        defer conn.Close()
    
        log.Println("start client")
    
        cli := pb.NewChatServiceClient(conn)
    
        sayHello(cli)
        //getMsgTotal(cli)
        //sendWord(cli)
        //chat(cli)
    }
    
    func sayHello(cli pb.ChatServiceClient)  {
        resp, err := cli.SayHello(ctx, &pb.Message{Body: "i am tom"})
        if err != nil{
            log.Fatal(err)
        }
    
        log.Println(resp.Body)
    }
    
    // 服务端流的方式推送消息
    func getMsgNum(cli pb.ChatServiceClient)  {
        stream, err := cli.GetMsgNum(ctx, &pb.ReqMsgNum{})
        if err != nil{
            log.Fatal(err)
        }
    
        for {
            resp, err := stream.Recv()
            if err != nil{
                log.Println(err)
                break
            }
    
            log.Println("msg total => ", resp.Num)
        }
    }
    
    
    // 客户端流的方式发送消息
    func sendWord(cli pb.ChatServiceClient){
        stream, err := cli.MatchWord(ctx)
        if err != nil{
            log.Fatal(err)
        }
        var sendStr string = "abc"
        for i := 0; i < 5; i++{
            log.Println("send str => ", sendStr)
            err := stream.Send(&pb.ReqMatchWord{Word: sendStr})
            if err != nil{
                log.Fatal(err)
                return
            }
    
            sendStr += "good"
            time.Sleep(time.Second)
        }
    
        resp, err := stream.CloseAndRecv()
        if err != nil{
            log.Println(err)
            return
        }
    
        log.Println("the word match => ", resp.IsMatch)
    }
    
    // 双向流收发消息
    func chat(cli pb.ChatServiceClient) {
        stream, err := cli.Chat(ctx)
        if err != nil{
            log.Fatal(err)
        }
    
        var i int
        var msg string
        for i < 100 {
            msg = fmt.Sprintf("chat msg %d", i)
            fmt.Println("send msg => ", msg)
            err := stream.Send(&pb.Message{Body: msg})
            if err != nil{
                fmt.Println(err)
                break
            }
    
            resp, err := stream.Recv()
            if err != nil{
                fmt.Println(err)
                break
            }
    
            log.Println("recv msg => ", resp.Body)
    
            time.Sleep(time.Second * 2)
            i++
        }
    }
    

    遇到问题:

    1. 使用protoc生成go文件提示 protoc-gen-go: unable to determine Go import path,

    该问题出现原因是没有正确设置option go_package。在proto文件中添加option go_package = "./;pb";其中pb是包名,可以自定义, ./表示当前目录。该选项主要是用于配置包依赖路径,例如a.proto imports b.proto,则生成的pd.go文件也有依赖关系,因此要设置该路径。

    2. 运行时提示

    undefined: grpc.SupportPackageIsVersion7
    undefined: grpc.ClientConnInterface
    undefined: grpc.ClientConnInterface
    undefined: grpc.ServiceRegistrar
    

    出现这个是因为 grpc和proto-gen-go 的版本问题导致的,两种解决方案。

    1. 升级grpc版本1.27或以上,笔者这里升级到1.29.1
    replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
    
    1. 降级protoc-gen-go的版本
    go get -u github.com/golang/protobuf/protoc-gen-go是安装最新版的protoc-gen-go
    
    降低protoc-gen-go的具体办法,在终端运行如下命令,这里降低到版本 v1.2.0
    
    GIT_TAG="v1.2.0"
    go get -d -u github.com/golang/protobuf/protoc-gen-go
    git -C "$(go env GOPATH)"/src/github.com/golang/protobuf checkout $GIT_TAG
    go install github.com/golang/protobuf/protoc-gen-go
    

    3. proto 请求参数或者响应参数为空

    通过引入 empty.proto,具体看示例

    syntax = "proto3";
    
    import "google/protobuf/empty.proto";
    
    package proto;
    
    message ReqHello {
      string message = 1;
    }
    message RespHello {
      string message = 1;
    }
    
    service Greeter {
      // 没有返回值 情况
      rpc Hello1(ReqHello) returns (google.protobuf.Empty) {}
      // 没有参数 情况
      rpc Hello2 (google.protobuf.Empty) returns (RespHello) {}
      // 没有参数,没有返回值 情况
      rpc Hello3 (google.protobuf.Empty) returns (google.protobuf.Empty) {}
    }
    
    
    
    

    相关文章

      网友评论

          本文标题:gRpc

          本文链接:https://www.haomeiwen.com/subject/tqcysrtx.html