文章本身不會特別解釋gRPC 與protocol buffers,如果有興趣可以去看官網文件會更精準一點 grpc.io,主體還是放在如何實作gRPC server/client與定義protobuf
簡單說一下為什麼要用gRPC
1.HTTP/2 進行通訊協定,效能好
2.Protobuf為二進位而且檔案小
3.如果傳輸大量資料時,gRPC比較不會炸掉
4.相對安全,因為RESTFUL API只要猜到METHOD,HEADER就有機會try到,可是用gRPC的話,Client也要拿的到protobuf才有辦法傳遞,如果protobuf放在內部repo,要被外部try到機率已經低很多惹!!
1.Protocol Compiler Installation:官方安裝文件
2.Go protocol buffers plugin:go get github.com/golang/protobuf/protoc-gen-go
3.Golang grpc package:go get -u google.golang.org/grpc
安裝後可以試看看是否環境都設定好
$protoc --version
//libprotoc 3.12.3
目前使用的版本為v3,所以syntax要指定proto3
syntax = "proto3";
package mdfk2020;
//定義Service名稱,
service SaySomethingService{
//定義api名稱,傳入參數與回傳值
rpc MDFK2020 (MDFKRequest) returns (MDFKResponse) {}
}
//傳入參數的spec
message MDFKRequest {
string name = 1;
}
//回傳值的spec
message MDFKResponse {
string data = 1;
}
定義好之後執行
$protoc --go_out=plugins=grpc:. *.proto
目前版本會跳出一個提示訊息
WARNING: Missing 'go_package' option in "MDFK2020.proto", please specify:
option go_package = ".;mdfk2020";
A future release of protoc-gen-go will require this be specified.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.
但是還是會產生檔案,如果不要跳出提示訊息的話,需要在proto裡面加入option go_package = ".;mdfk2020";
syntax = "proto3";
option go_package = ".;mdfk2020";
package mdfk2020;
會產生一個.pb.go的檔案,打開看會發現protoc cli會把proto定義好的service、func、resquest與response產生成go的檔案
如果pb檔不是放在github上面,而是放在local要import進來用的話,要記得import的路徑,
import的路徑是以設定好的gopath進來判斷,以下圖為例子
//完整路徑~/GoProjects/src/internal/grpcdemo/pb
import pb "internal/grpcdemo/pb"
1.實作proto定義好的service
2.運行gRPC服務
package main
import (
"context"
"fmt"
"log"
"net"
pb "internal/grpcdemo/pb"
"google.golang.org/grpc"
)
const (
port = ":8787"
)
type server struct{}
func main() {
// Create gRPC Server
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
log.Println("gRPC server is running.")
pb.RegisterSaySomethingServiceServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
//
func (s *server) MDFK2020(ctx context.Context, in *pb.MDFKRequest) (*pb.MDFKResponse, error) {
fmt.Println("i receive:", in.Name)
return &pb.MDFKResponse{Data: "Hello " + in.Name}, nil
}
client的使用流程相對單純,使用grpc.Dial()連接gRPC Server,在用回傳的conn去呼叫定義好的service ,參數與回傳值都跟proto定義好的一致就行。
package main
import (
"context"
pb "internal/grpcdemo/pb"
"log"
"time"
"google.golang.org/grpc"
)
const (
address = "localhost:8787"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewSaySomethingServiceClient(conn)
mdfk2020(c, "GG")
}
//
func mdfk2020(c pb.SaySomethingServiceClient, name string) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := c.MDFK2020(ctx, &pb.MDFKRequest{Name: name})
if err != nil {
log.Fatalf("could not mkdfk2020: %v", err)
}
log.Printf("gRPC response: %s", res.Data)
}
執行結果
//server
i receive: GG
//client
2020/09/28 13:59:08 gRPC response: Hello GG
gRPC實作上面是比RESTful API麻煩一點,因為多了要先定義好proto,如果要加個欄位變成
1.proto加好欄位
2.進行cli產生新的pb檔
3.server端引用新的pb檔
4.client端引用新的pb檔
視自己工作的需求進行選擇吧~~