gRPC雙向數(shù)據(jù)流的交互控制(go語言實(shí)現(xiàn))| gRPC雙向數(shù)據(jù)流的交互控制系列(1)

gRPC簡介

gRPC (https://grpc.io) 是一個(gè)由Google開發(fā)的高性能窿锉、開源、跨多種編程語言和通用的遠(yuǎn)程過程調(diào)用協(xié)議(RPC) 框架,用于客戶端和服務(wù)器端之間的通信荠雕,使用HTTP/2協(xié)議并將 ProtoBuf (https://developers.google.com/protocol-buffers)作為序列化工具轩拨。


gRPC模式

gRPC主要有4種請(qǐng)求/響應(yīng)模式,分別是:

(1) 簡單模式(Simple RPC)

這種模式最為傳統(tǒng)换衬,即客戶端發(fā)起一次請(qǐng)求痰驱,服務(wù)端響應(yīng)一個(gè)數(shù)據(jù),這和大家平時(shí)熟悉的RPC沒有什么大的區(qū)別瞳浦,所以不再詳細(xì)介紹担映。

(2) 服務(wù)端數(shù)據(jù)流模式(Server-side streaming RPC)

這種模式是客戶端發(fā)起一次請(qǐng)求,服務(wù)端返回一段連續(xù)的數(shù)據(jù)流叫潦。典型的例子是客戶端向服務(wù)端發(fā)送一個(gè)股票代碼蝇完,服務(wù)端就把該股票的實(shí)時(shí)數(shù)據(jù)源源不斷的返回給客戶端。

(3) 客戶端數(shù)據(jù)流模式(Client-side streaming RPC)

與服務(wù)端數(shù)據(jù)流模式相反诅挑,這次是客戶端源源不斷的向服務(wù)端發(fā)送數(shù)據(jù)流四敞,而在發(fā)送結(jié)束后,由服務(wù)端返回一個(gè)響應(yīng)拔妥。典型的例子是物聯(lián)網(wǎng)終端向服務(wù)器報(bào)送數(shù)據(jù)忿危。

(4) 雙向數(shù)據(jù)流模式(Bidirectional streaming RPC)

顧名思義,這是客戶端和服務(wù)端都可以向?qū)Ψ桨l(fā)送數(shù)據(jù)流没龙,這個(gè)時(shí)候雙方的數(shù)據(jù)可以同時(shí)互相發(fā)送铺厨,也就是可以實(shí)現(xiàn)實(shí)時(shí)交互。典型的例子是聊天機(jī)器人硬纤。


雙向數(shù)據(jù)流實(shí)戰(zhàn)

在gRPC中文文檔(http://doc.oschina.net/grpc?t=60133)中有上述4種模式的實(shí)例解滓,但是其中雙向數(shù)據(jù)流的例子過于簡單,沒有體現(xiàn)出雙向控制的特點(diǎn)筝家,所以本文創(chuàng)建一個(gè)新的例子(用golang實(shí)現(xiàn))洼裤,用以展示gRPC雙向數(shù)據(jù)流的交互(關(guān)于proto如何定義、相關(guān)包如何安裝溪王,在文檔中都有介紹腮鞍,所以本文略去此部分)值骇。

1、proto定義

syntax = "proto3"; // 語法使用 protocol buffer proto3

// 包名: chat
package chat;   

/*
    服務(wù)名: Chat移国,
    其中只有 名為“BidStream”的一個(gè)RPC服務(wù)吱瘩,
    輸入是 Request格式的數(shù)據(jù)流, 輸出是 Response 格式的數(shù)據(jù)流
*/
service Chat {  
    rpc BidStream(stream Request) returns (stream Response) {}
}

// 請(qǐng)求數(shù)據(jù) Request格式定義
message Request {
    string input = 1;
}

// 響應(yīng)數(shù)據(jù)Response格式定義
message Response {
    string output = 1;
}

服務(wù)端程序 server.go

package main

import (
    "io"
    "log"
    "net"
    "strconv"
    "google.golang.org/grpc"
    proto "chat" // 自動(dòng)生成的 proto代碼
)

// Streamer 服務(wù)端
type Streamer struct{}

// BidStream 實(shí)現(xiàn)了 ChatServer 接口中定義的 BidStream 方法
func (s *Streamer) BidStream(stream proto.Chat_BidStreamServer) error {
    ctx := stream.Context()
    for {
        select {
        case <-ctx.Done():
            log.Println("收到客戶端通過context發(fā)出的終止信號(hào)")
            return ctx.Err()
        default:
            // 接收從客戶端發(fā)來的消息
            輸入, err := stream.Recv()
            if err == io.EOF {
                log.Println("客戶端發(fā)送的數(shù)據(jù)流結(jié)束")
                return nil
            }
            if err != nil {
                log.Println("接收數(shù)據(jù)出錯(cuò):", err)
                return err
            }

            // 如果接收正常迹缀,則根據(jù)接收到的 字符串 執(zhí)行相應(yīng)的指令
            switch 輸入.Input {
            case "結(jié)束對(duì)話\n":
                log.Println("收到'結(jié)束對(duì)話'指令")
                if err := stream.Send(&proto.Response{Output: "收到結(jié)束指令"}); err != nil {
                    return err
                }
                // 收到結(jié)束指令時(shí)使碾,通過 return nil 終止雙向數(shù)據(jù)流
                return nil

            case "返回?cái)?shù)據(jù)流\n":
                log.Println("收到'返回?cái)?shù)據(jù)流'指令")
                // 收到 收到'返回?cái)?shù)據(jù)流'指令, 連續(xù)返回 10 條數(shù)據(jù)
                for i := 0; i < 10; i++ {
                    if err := stream.Send(&proto.Response{Output: "數(shù)據(jù)流 #" + strconv.Itoa(i)}); err != nil {
                        return err
                    }
                }

            default:
                // 缺省情況下祝懂, 返回 '服務(wù)端返回: ' + 輸入信息
                log.Printf("[收到消息]: %s", 輸入.Input)
                if err := stream.Send(&proto.Response{Output: "服務(wù)端返回: " + 輸入.Input}); err != nil {
                    return err
                }
            }
        }
    }
}

func main() {
    log.Println("啟動(dòng)服務(wù)端...")
    server := grpc.NewServer()

    // 注冊(cè) ChatServer
    proto.RegisterChatServer(server, &Streamer{})

    address, err := net.Listen("tcp", ":3000")
    if err != nil {
        panic(err)
    }

    if err := server.Serve(address); err != nil {
        panic(err)
    }
}

客戶端程序 client.go

package main

import (
    "bufio"
    "context"
    "io"
    "log"
    "os"

    "google.golang.org/grpc"
    proto "chat" // 根據(jù)proto文件自動(dòng)生成的代碼
)

func main() {
    // 創(chuàng)建連接
    conn, err := grpc.Dial("localhost:3000", grpc.WithInsecure())
    if err != nil {
        log.Printf("連接失敗: [%v]\n", err)
        return
    }
    defer conn.Close()
    
    // 聲明客戶端
    client := proto.NewChatClient(conn)

    // 聲明 context
    ctx := context.Background()

    // 創(chuàng)建雙向數(shù)據(jù)流
    stream, err := client.BidStream(ctx)
    if err != nil {
        log.Printf("創(chuàng)建數(shù)據(jù)流失敗: [%v]\n", err)
    }

    // 啟動(dòng)一個(gè) goroutine 接收命令行輸入的指令
    go func() {
        log.Println("請(qǐng)輸入消息...")
        輸入 := bufio.NewReader(os.Stdin)
        for {
            // 獲取 命令行輸入的字符串票摇, 以回車 \n 作為結(jié)束標(biāo)志
            命令行輸入的字符串, _ := 輸入.ReadString('\n')

            // 向服務(wù)端發(fā)送 指令
            if err := stream.Send(&proto.Request{Input: 命令行輸入的字符串}); err != nil {
                return
            }
        }
    }()

    for {
        // 接收從 服務(wù)端返回的數(shù)據(jù)流
        響應(yīng), err := stream.Recv()
        if err == io.EOF {
            log.Println("?? 收到服務(wù)端的結(jié)束信號(hào)")
            break   //如果收到結(jié)束信號(hào),則退出“接收循環(huán)”嫂易,結(jié)束客戶端程序
        }

        if err != nil {
            // TODO: 處理接收錯(cuò)誤
            log.Println("接收數(shù)據(jù)出錯(cuò):", err)
        }
        
        // 沒有錯(cuò)誤的情況下兄朋,打印來自服務(wù)端的消息
        log.Printf("[客戶端收到]: %s", 響應(yīng).Output)
    }
}

運(yùn)行效果

先啟動(dòng)服務(wù)端程序 server.go
再啟動(dòng)客戶端程序 client.go

輸入消息,結(jié)果類似下圖:

運(yùn)行截圖

總結(jié)

gRPC是個(gè)很強(qiáng)大的RPC框架怜械,而且支持多語言編程,上面的服務(wù)端傅事、客戶端程序我們完全可以用不同的語言實(shí)現(xiàn)缕允,比如服務(wù)端用JAVA,客戶端用Python...

gRPC的四種交互模式也給我們提供了很大的發(fā)揮空間蹭越,最近Nginx宣布支持gRPC障本,這可能也預(yù)示著某種趨勢(shì)...


nginx + grpc
golang-pic.png

gRPC雙向數(shù)據(jù)流的交互控制系列

(之二): 通過Websocket與gRPC交互
(之三): 通過Nginx實(shí)現(xiàn)gRPC服務(wù)的負(fù)載均衡

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市响鹃,隨后出現(xiàn)的幾起案子驾霜,更是在濱河造成了極大的恐慌,老刑警劉巖买置,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粪糙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡忿项,警方通過查閱死者的電腦和手機(jī)蓉冈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轩触,“玉大人寞酿,你說我怎么就攤上這事⊥阎” “怎么了伐弹?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長榨为。 經(jīng)常有香客問我惨好,道長煌茴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任昧狮,我火速辦了婚禮景馁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逗鸣。我一直安慰自己合住,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布撒璧。 她就那樣靜靜地躺著透葛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卿樱。 梳的紋絲不亂的頭發(fā)上僚害,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音繁调,去河邊找鬼萨蚕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蹄胰,可吹牛的內(nèi)容都是我干的岳遥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼裕寨,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浩蓉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宾袜,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤捻艳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后庆猫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體认轨,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年阅悍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了好渠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡节视,死狀恐怖拳锚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情寻行,我是刑警寧澤霍掺,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響杆烁,放射性物質(zhì)發(fā)生泄漏牙丽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一兔魂、第九天 我趴在偏房一處隱蔽的房頂上張望烤芦。 院中可真熱鬧,春花似錦析校、人聲如沸构罗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遂唧。三九已至,卻和暖如春吊奢,著一層夾襖步出監(jiān)牢的瞬間盖彭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工页滚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留召边,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓裹驰,卻偏偏與公主長得像掌实,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邦马,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • GRPC是基于protocol buffers3.0協(xié)議的. 本文將向您介紹gRPC和protocol buffe...
    二月_春風(fēng)閱讀 17,991評(píng)論 2 28
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)宴卖,斷路器滋将,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 本文通過gRPC的結(jié)構(gòu)概述和生命周期介紹一些gRPC理念的關(guān)鍵點(diǎn)。 概述 服務(wù)定義 就像很多RPC系統(tǒng)一樣症昏,gRP...
    竹天亮閱讀 1,904評(píng)論 1 3
  • 轉(zhuǎn)自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志閱讀 24,808評(píng)論 2 38
  • 本來打算完成國慶計(jì)劃再來打卡的随闽,不過翻到明天的任務(wù)時(shí),發(fā)現(xiàn)居然到16單元了肝谭,我已經(jīng)完成紅寶書的3/4了掘宪,立刻興奮地...
    LucindaAvery閱讀 165評(píng)論 0 1