gRpc

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) {}
}



©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容