本文章同時發佈於:
文章為自己的經驗與夥伴整理的內容,設計沒有標準答案,如有可以改進的地方,請告訴我,我會盡我所能的修改,謝謝大家~
大家好,繼昨天DAY09的介紹後,gRPC 相信大家已經稍有概念,接下來將介紹四種 gRPC 的傳輸方式:
- 單向傳輸(Unary): Client 單向,Server 單向
- Server 單向串流(Streaming-Server): Client 單向,Server 串流
- Client 單向串流(Streaming-Client): Client 串流,Server 單向
- 雙向串流(Streaming-Bidirectional): Client 串流,Server 串流
會以一個 Golang Client 一個 Golang Server 來講解。
以下講解 code 全部都放在DAY10-Github
建立 Protobuf schema
如DAY09所述,必須先建立好一個 Protobuf schema,之後我們就可以用此 schema 產生 Client 與 Server 的 code。
unary
傳輸
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
streaming-server
傳輸
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
streaming-client
傳輸
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (stream HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
streaming-bidirectional
傳輸
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (stream HelloRequest) returns (stream HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
syntax
: 為 protobuf 的版本,目前常見的 proto2 與 proto3,在 require 這個關鍵字上做了很大的變動,詳情改動可以看設計思考 — Protocol Buffers 3 為什麼。package
: 為產出來 code 的 packge name。message
: 為 protobuf 的訊息,你可以想像是 Restful API 的 request 與 response,但是介面更為嚴謹。service
: gRPC 的方法由此定義,以rpc SayHello (stream HelloRequest) returns (HelloReply) {}
來說,SayHello
是方法名,HelloRequest
為參數,HelloReply
為 result,有無stream
代表了是否要用串流還是單向。
設計好了之後就可以執行以下 script 產生出gen
資料夾,裡面就有實際的 code 了。
$ build-proto.sh
實際運作
package main
import (
"context"
"log"
"net"
pb "unary/gen/grpc-gateway/gen"
"google.golang.org/grpc"
)
const (
port = ":50052"
)
// SayHello implements helloworld.GreeterServer.SayHello
func (s *Server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
// Server ...
type Server struct {
pb.UnimplementedGreeterServer
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &Server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
以 unary 來說,將要import pb "unary/gen/grpc-gateway/gen"
,並將pb.UnimplementedGreeterServer
實作,最後透過grpc.NewServer()
來創建 gRPC Server,並透過pb.RegisterGreeterServer(s, &Server{})
來註冊實作,最後以s.Serve(lis)
啟動 Server。
而四種傳輸方法的測試方式都是:
go run server/main.go
: 啟動 servergo run client/main.go
: 使用 client 來呼叫
都大同小異,實作細節可以看 code,我舉出比較要注意的地方,
srv pb.Greeter_SayHelloServer
為 Streaming 的方式,在 client 端呼叫後:
- 如果是 Streaming-Client: Server 可以透過此參數以
.Recv()
的方式持續接收資料,Client 可以透過此參數以.Send()
的方式持續傳送資料。 - 如果是 Streaming-Server: Server 可以透過此參數以
.Send()
的方式持續傳送資料,Client 可以透過此參數以.Recv()
的方式持續傳送資料。 - 如果是 Streaming-Bidirectional: Server 可以透過此參數以
.Send()
的方式持續傳送資料與.Recv()
的方式持續接收資料,Client 可以透過此參數以.Send()
的方式持續傳送資料與.Recv()
的方式持續接收資料。
參考
- Core concepts, architecture and lifecycle – gRPC
- grpc-go/examples at master · grpc/grpc-go
- Basics Tutorial – gRPC
- go-grpc-examples/client.go at master · itsksaurabh/go-grpc-examples
- 設計思考 — Protocol Buffers 3 為什麼. 簡單是一件非常難的事! 而深思熟慮過的簡單,可以給我們最多的思考與學習 | by Leon Tsai | Medium
- 比起 JSON 更方便、更快速、更簡短的 Protobuf 格式 – 電腦玩瞎咪