gRpc

gRPC(google+remote process call) 詳解

grpc 簡介

grpc是一個(gè)高性能、開源和通用的 RPC 框架琅锻,面向移動(dòng)和 HTTP/2 設(shè)計(jì)卦停。所謂RPC(remote procedure call 遠(yuǎn)程過程調(diào)用)框架實(shí)際是提供了一套機(jī)制,使得應(yīng)用程序之間可以進(jìn)行通信恼蓬,而且也遵從server/client模型惊完。使用的時(shí)候客戶端調(diào)用server端提供的接口就像是調(diào)用本地的函數(shù)一樣。如下圖所示就是一個(gè)典型的RPC結(jié)構(gòu)圖滚秩。
[圖片上傳失敗...(image-861e7e-1649389684397)]

  1. 客戶端使用RPC調(diào)用遠(yuǎn)程方法专执,客戶端的存根發(fā)起請求,對請求的數(shù)據(jù)使用protobuf對象序列化和壓縮郁油。
  2. 服務(wù)器接收到請求后本股,解碼請求數(shù)據(jù),使用protobuf反序列化為內(nèi)存對象桐腌,處理業(yè)務(wù)邏輯并返回響應(yīng)結(jié)果拄显。
  3. 客戶端收到服務(wù)端響應(yīng),解碼響應(yīng)數(shù)據(jù)案站,喚醒阻塞客戶端躬审。

grpc 使用

工欲善其事必先利其器,開發(fā)一個(gè)grpc示例之前先安裝好需要的工具和插件

  • 下載protoc工具 https://github.com/protocolbuffers/protobuf/releases/ 注意系統(tǒng)和版本, 下載完成后,執(zhí)行路徑添加到環(huán)境變量

  • 插件安裝
    go get -u github.com/golang/protobuf/protoc-gen-go蟆盐, GOBIN($GOPATH/bin)添加到環(huán)境變量即可1

grpc 示例

  1. 創(chuàng)建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){}
}

這里定義了四個(gè)方法
sayHello 普通的rpc調(diào)用
GetMsgNum 服務(wù)端流式調(diào)用承边,客戶端普通的rpc調(diào)用
MatchWord 客戶端流式調(diào)用,服務(wù)端普通rcp調(diào)用
Chat 雙向流式調(diào)用

  1. 生成對應(yīng)的go文件
protoc --go_out=plugins=grpc:. chat.proto
  1. 服務(wù)端代碼
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
}

// 服務(wù)端流推送消息數(shù)量
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
}

// 服務(wù)端接收客戶端流
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 業(yè)務(wù)處理 是否匹配單詞
        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)
}

// 服務(wù)端流的方式推送消息
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)
    }
}


// 客戶端流的方式發(fā)送消息
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)
}

// 雙向流收發(fā)消息
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石挂,

該問題出現(xiàn)原因是沒有正確設(shè)置option go_package博助。在proto文件中添加option go_package = "./;pb";其中pb是包名,可以自定義痹愚, ./表示當(dāng)前目錄富岳。該選項(xiàng)主要是用于配置包依賴路徑,例如a.proto imports b.proto,則生成的pd.go文件也有依賴關(guān)系拯腮,因此要設(shè)置該路徑窖式。

2. 運(yùn)行時(shí)提示

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

出現(xiàn)這個(gè)是因?yàn)?grpc和proto-gen-go 的版本問題導(dǎo)致的,兩種解決方案动壤。

  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的具體辦法,在終端運(yùn)行如下命令,這里降低到版本 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 請求參數(shù)或者響應(yīng)參數(shù)為空

通過引入 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) {}
  // 沒有參數(shù) 情況
  rpc Hello2 (google.protobuf.Empty) returns (RespHello) {}
  // 沒有參數(shù)阁簸,沒有返回值 情況
  rpc Hello3 (google.protobuf.Empty) returns (google.protobuf.Empty) {}
}



?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弦蹂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子强窖,更是在濱河造成了極大的恐慌凸椿,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翅溺,死亡現(xiàn)場離奇詭異脑漫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)咙崎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門优幸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人褪猛,你說我怎么就攤上這事网杆。” “怎么了伊滋?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵碳却,是天一觀的道長。 經(jīng)常有香客問我笑旺,道長昼浦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任筒主,我火速辦了婚禮关噪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乌妙。我一直安慰自己使兔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布藤韵。 她就那樣靜靜地躺著虐沥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荠察。 梳的紋絲不亂的頭發(fā)上置蜀,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天奈搜,我揣著相機(jī)與錄音悉盆,去河邊找鬼。 笑死馋吗,一個(gè)胖子當(dāng)著我的面吹牛焕盟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脚翘,長吁一口氣:“原來是場噩夢啊……” “哼灼卢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起来农,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鞋真,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后沃于,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涩咖,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年繁莹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了檩互。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咨演,死狀恐怖闸昨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薄风,我是刑警寧澤饵较,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站遭赂,受9級特大地震影響告抄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嵌牺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一打洼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逆粹,春花似錦募疮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹋绽,卻和暖如春芭毙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卸耘。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工退敦, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚣抗。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓侈百,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钝域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容