gRPC應用實戰:(三)gRPC四種請求模式

CodeFish-xiao發表於2021-09-12

3.1 前言

gRPC主要有4種請求和響應模式,分別是簡單模式(Simple RPC)、服務端流式(Server-side streaming RPC)、客戶端流式(Client-side streaming
RPC)、和雙向流式(Bidirectional streaming RPC)。其實好多顧名思義就可以知道相關資訊:

  • 簡單模式:又稱為一元 RPC,在上一節的時候,我們的例子就是簡單模式,類似於常規的http請求,客戶端傳送請求,服務端響應請求
  • 服務端流式:客戶端傳送請求到伺服器,拿到一個流去讀取返回的訊息序列。 客戶端讀取返回的流,直到裡面沒有任何訊息。
  • 客戶端流式:與服務端資料流模式相反,這次是客戶端源源不斷的向服務端傳送資料流,而在傳送結束後,由服務端返回一個響應。
  • 雙向流式:雙方使用讀寫流去傳送一個訊息序列,兩個流獨立操作,雙方可以同時傳送和同時接收。

不同的呼叫方式往往代表著不同的應用場景,接下來我們就把剩下的三種來實操一遍:

溫馨提示:以下的所有程式碼,都在 這裡 ,所有的pb檔案都在pb包中。

3.2 服務端流式 RPC(Server-side streaming RPC)

伺服器端流式 RPC,也就是是單向流,並代指 Server 為 Stream,Client 為普通的一元 RPC 請求。

3.2.1 proto

其實關鍵就是在服務端返回的資料前加上 stream 關鍵字

//一個為ServerSide的服務
service ServerSide {
  //一個ServerSideHello的方法
  rpc ServerSideHello (ServerSideRequest) returns (stream ServerSideResp) {}
}

然後執行 protoc --go_out=plugins=grpc:. *.proto 生成對應的程式碼。

3.2.2 實現服務端程式碼

3.2.2.1 定義我們的服務

首先定義我們的服務 ServerSideService 並且實現ServerSideHello方法。

type ServerSideService struct {
}

func (s *ServerSideService) ServerSideHello(request *pb.ServerSideRequest, server pb.ServerSide_ServerSideHelloServer) error {
    log.Println(request.Name)
    for n := 0; n < 5; n++ {
        // 向流中傳送訊息, 預設每次send送訊息最大長度為`math.MaxInt32`bytes 
        err := server.Send(&pb.ServerSideResp{Message: "你好"})
        if err != nil {
            return err
        }
    }
    return nil
}

然後在 server包 中註冊service

    pb.RegisterServerSideServer(grpcServer, &services.ServerSideService{})

3.2.2.2 執行我們的服務

這是全部的server的資訊,後面就不再重複這一部分的資訊了,通過 grpc.NewServer() 建立新的gRPC伺服器,之後進行對應的服務註冊,並且呼叫 grpc.NewServer() 阻塞執行緒。

package main

import (
    "github.com/CodeFish-xiao/blogs/gRPCAction/code/grpc-3/pb"
    "github.com/CodeFish-xiao/blogs/gRPCAction/code/grpc-3/services"
    "google.golang.org/grpc"
    "log"
    "net"
)

const (
    // Address 監聽地址
    Address string = ":8546"
    // Network 網路通訊協議
    Network string = "tcp"
)

func main() {
    // 監聽本地埠
    listener, err := net.Listen(Network, Address)
    if err != nil {
        log.Panic("net.Listen err: %v", err)
    }
    log.Println(Address + " net.Listing...")
    // 新建gRPC伺服器例項
    grpcServer := grpc.NewServer()
    // 在gRPC伺服器註冊我們的服務
    pb.RegisterClientSideServer(grpcServer, &services.BidirectionalService{})
    pb.RegisterServerSideServer(grpcServer, &services.ServerSideService{})
    pb.RegisterBidirectionalServer(grpcServer, &services.ClientSideService{})
    //用伺服器 Serve() 方法以及我們的埠資訊區實現阻塞等待,直到程式被殺死或者 Stop() 被呼叫
    err = grpcServer.Serve(listener)
    if err != nil {
        log.Panic("grpcServer.Serve err: %v", err)
    }
}

執行後:

4pHWSf.png

3.2.2 實現客戶端程式碼

程式碼如下:

const (
// ServerAddress 連線地址
ServerAddress string = ":8546"
)

func main() {
    ServerSide()
}

func ServerSide() {
// 連線伺服器
    conn, err := grpc.Dial(ServerAddress, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("net.Connect err: %v", err)
    }
    defer conn.Close()

    // 建立gRPC連線
    grpcClient := pb.NewServerSideClient(conn)
    // 建立傳送結構體
    req := pb.ServerSideRequest{
        Name: "我來開啟你啦",
    }
    //獲取流
    stream, err := grpcClient.ServerSideHello(context.Background(), &req)
    if err != nil {
        log.Fatalf("Call SayHello err: %v", err)
    }
    for n := 0; n < 5; n++ {
        res, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("Conversations get stream err: %v", err)
        }
        // 列印返回值
        log.Println(res.Message)
    }
}

因為是伺服器流模式,需要先從伺服器獲取流,也就是連結,通過流進行資料傳輸,客戶端通過 Recv() 獲取服務端的資訊,然後輸出

3.2.3 服務流模式執行樣例

編寫完客戶端程式碼後,執行可見: 傳送一次請求後,收取服務端發來的請求資訊

客戶端:
4pqoin.png

服務端:

收到一次客戶端的請求後,傳送資訊。

4pLUWq.png

3.3 客戶端流式 RPC(Client-side streaming RPC)

客戶端流式 RPC,也是單向流,不過是由客戶端傳送流式資料罷了。

3.3.1 proto

其實關鍵就是在客戶端傳送的資料前資料前加上 stream 關鍵字

service ClientSide {
  //一個ClientSideHello的方法
  rpc ClientSideHello (stream ClientSideRequest) returns (ClientSideResp) {}
}

然後執行 protoc --go_out=plugins=grpc:. *.proto 生成對應的程式碼。

3.3.2 實現服務端程式碼

實現程式碼的話,跟客戶端流模式程式碼大同小異。

3.3.2.1 定義我們的服務

首先定義我們的服務 ClientSideService 並且實現ClientSideHello方法。

type ClientSideService struct {
}

func (c *ClientSideService) ClientSideHello(server pb.ClientSide_ClientSideHelloServer) error {
    for i := 0; i < 5; i++ {
        recv, err := server.Recv()
        if err != nil {
            return err
        }
        log.Println("客戶端資訊:", recv)
    }
    //服務端最後一條訊息傳送
    err := server.SendAndClose(&pb.ClientSideResp{Message: "關閉"})
    if err != nil {
        return err
    }
    return nil
}

然後在 server包 中註冊service

    pb.RegisterClientSideServer(grpcServer, &services.ClientSideService{})

3.3.2.2 執行我們的服務

執行後:

49P4dx.png

3.3.2 實現客戶端程式碼

程式碼如下:

func ClientSide() {
    // 連線伺服器 
    conn, err := grpc.Dial(ServerAddress, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("net.Connect err: %v", err)
    }
    defer conn.Close()

    // 建立gRPC連線 
    grpcClient := pb.NewClientSideClient(conn)
// 建立傳送結構體 
    res, err := grpcClient.ClientSideHello(context.Background())
    if err != nil {
        log.Fatalf("Call SayHello err: %v", err)
    }
    for i := 0; i < 5; i++ {
    //通過 Send方法傳送流資訊 
        err = res.Send(&pb.ClientSideRequest{Name: "客戶端流式"})
        if err != nil {
            return
        }
    }
    // 列印返回值 
    log.Println(res.CloseAndRecv())
}

3.3.3 客戶端流模式執行樣例

編寫完客戶端程式碼後,執行可見:客戶端傳送流請求,之後服務端進行列印,5次後服務端傳送關閉流資訊,客戶端收到關閉資訊,並且關閉了流:

客戶端:

49POOA.png

服務端:

49PxTP.png

3.4 雙向流式 RPC(Bidirectional streaming RPC)

客戶端和服務端雙方使用讀寫流去傳送一個訊息序列,兩個流獨立操作,雙方可以同時傳送和同時接收。

3.4.1 proto

在請求值和返回值前加上 stream 關鍵字

service Bidirectional {
  //一個BidirectionalHello的方法
  rpc BidirectionalHello (stream BidirectionalRequest) returns (stream BidirectionalResp) {}
}

然後執行 protoc --go_out=plugins=grpc:. *.proto 生成對應的程式碼。

3.4.2 實現服務端程式碼

3.4.2.1 定義我們的服務

首先定義我們的服務 BidirectionalService 並且實現BidirectionalHello方法。

type BidirectionalService struct {
}

func (b *BidirectionalService) BidirectionalHello(server pb.Bidirectional_BidirectionalHelloServer) error {
    defer func() {
        log.Println("客戶端斷開連結")
    }()
    for  {
        //獲取客戶端資訊 
        recv, err := server.Recv()
        if err != nil {
            return err
        }
        log.Println(recv) 
        //傳送服務端資訊 
        err = server.Send(&pb.BidirectionalResp{Message: "服務端資訊"})
        if err != nil {
            return err
        }
    }
}

然後在 server包 中註冊service

    pb.RegisterBidirectionalServer(grpcServer, &services.BidirectionalService{})

3.4.2.2 執行我們的服務

執行後:
49P4dx.png

3.4.2 實現客戶端程式碼

程式碼如下:

func Bidirectional() {
    // 連線伺服器 
    conn, err := grpc.Dial(ServerAddress, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("net.Connect err: %v", err)
    }
    defer conn.Close() 
    // 建立gRPC連線 
    grpcClient := pb.NewBidirectionalClient(conn) 
    //獲取流資訊 
    stream, err := grpcClient.BidirectionalHello(context.Background())
    if err != nil {
        log.Fatalf("get BidirectionalHello stream err: %v", err)
    }

    for n := 0; n < 5; n++ {
        err := stream.Send(&pb.BidirectionalRequest{Name: "雙向流 rpc " + strconv.Itoa(n)})
        if err != nil {
            log.Fatalf("stream request err: %v", err)
        }
        res, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("Conversations get stream err: %v", err)
        }
        // 列印返回值 
        log.Println(res.Message) 
        }
}

雙向流模式,客戶端需要從服務端獲取流連結,之後雙方都可以通過該流進行傳輸

3.4.3 雙向流模式執行樣例

因為grpc處理了斷開連結後的處理,所以在客戶端斷開後,defer的程式碼可以執行並且輸出資訊。

客戶端:
498DTf.png

服務端:
4986fg.png

3.5 小結

簡單模式在上一節已經有說過,這次將其他幾個互動模式都闡述了一遍,基本對大部分業務場景都夠用了。但是在實際開發中,我們更多會需要很多東西:超時控制,負載均衡,許可權控制,資料驗證等功能,後續將會慢慢道來。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
程式碼小雜魚

相關文章