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)
}
}
執行後:
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 服務流模式執行樣例
編寫完客戶端程式碼後,執行可見: 傳送一次請求後,收取服務端發來的請求資訊
服務端:
收到一次客戶端的請求後,傳送資訊。
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 執行我們的服務
執行後:
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次後服務端傳送關閉流資訊,客戶端收到關閉資訊,並且關閉了流:
客戶端:
服務端:
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 執行我們的服務
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的程式碼可以執行並且輸出資訊。
3.5 小結
簡單模式在上一節已經有說過,這次將其他幾個互動模式都闡述了一遍,基本對大部分業務場景都夠用了。但是在實際開發中,我們更多會需要很多東西:超時控制,負載均衡,許可權控制,資料驗證等功能,後續將會慢慢道來。
本作品採用《CC 協議》,轉載必須註明作者和本文連結