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é)果類似下圖:
總結(jié)
gRPC是個(gè)很強(qiáng)大的RPC框架怜械,而且支持多語言編程,上面的服務(wù)端傅事、客戶端程序我們完全可以用不同的語言實(shí)現(xiàn)缕允,比如服務(wù)端用JAVA,客戶端用Python...
gRPC的四種交互模式也給我們提供了很大的發(fā)揮空間蹭越,最近Nginx宣布支持gRPC障本,這可能也預(yù)示著某種趨勢(shì)...
gRPC雙向數(shù)據(jù)流的交互控制系列
(之二): 通過Websocket與gRPC交互
(之三): 通過Nginx實(shí)現(xiàn)gRPC服務(wù)的負(fù)載均衡