1. 程式人生 > >動手實現一個簡單的 rpc 框架到入門 grpc (下)

動手實現一個簡單的 rpc 框架到入門 grpc (下)

之前手動實現了一次簡陋的 rpc 呼叫,為了簡單使用了 json 編碼資訊,其實這是非常不可靠的,go 中 json 解析會有一些問題,比如整數會變成浮點數,而且 json 字串比較佔空間。 gRPC 由 google 開發,是一款語言中立、平臺中立、開源的 RPC 框架,預設使用 protocol buffers 來序列化和傳輸訊息,基於 http2。 ## protobuf Protocol Buffers 是一種輕便高效的結構化資料儲存格式,可以用於結構化資料序列化,或者說序列化。它很適合做資料儲存或 RPC 資料交換格式。可用於通訊協議、資料儲存等領域的語言無關、平臺無關、可擴充套件的序列化結構資料格式。 先編寫 proto 檔案,再編譯成 go 檔案,新建 `proto/hello.proto` 檔案 ```go // proto/hello.proto syntax = "proto3"; package proto; message String { string value = 1; // 型別 欄位 欄位識別符號 } // 定義服務 service HelloService { rpc Hello (String) returns (String); } ``` 安裝 protobuf 編譯器用於編譯 proto 檔案,比如使用 scoop 安裝 `scoop install protobuf`,或者手動下載安裝 protobuf 編譯工具可以把 proto 模板編譯成多種語言,預設不支援 go,安裝一下 go 外掛 ```sh $ go get -u github.com/golang/protobuf/proto $ go get -u github.com/golang/protobuf/protoc-gen-go ``` 進入 `hello.proto` 檔案目錄,編譯生成 `hello.pb.go`,後續為了方便可以把編譯指令寫成 shell 指令碼 ```sh $ protoc -I . --go_out=plugins=grpc:. ./hello.proto ``` 生成的 `hello.pb.go` 有一些介面和方法(省略了其他的),使用時候只需要實現介面呼叫對應方法 ```go type HelloServiceServer interface { Hello(context.Context, *String) (*String, error) } func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) { s.RegisterService(&_HelloService_serviceDesc, srv) } type helloServiceClient struct { cc grpc.ClientConnInterface } func NewHelloServiceClient(cc grpc.ClientConnInterface) HelloServiceClient { return &helloServiceClient{cc} } func (c *helloServiceClient) Hello(ctx context.Context, in *String, opts ...grpc.CallOption) (*String, error) { out := new(String) err := c.cc.Invoke(ctx, "/proto.HelloService/Hello", in, out, opts...) if err != nil { return nil, err } return out, nil } ``` ## grpc 安裝 grpc ```sh $ go get -u google.golang.org/grpc ``` ### 服務端 在 proto 資料夾同級新建 `sever/server.go` 檔案,實現 server 介面,註冊服務 ```go type HelloServiceImpl struct{} func (p *HelloServiceImpl) Hello(ctx context.Context, args *proto.String) (*proto.String, error) { reply := &proto.String{Value: "hello " + args.GetValue()} return reply, nil } func main() { grpcServer := grpc.NewServer() proto.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl)) lis, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal(err) } grpcServer.Serve(lis) } ``` ### 客戶端 在 proto 資料夾同級新建 `sever/server.go` 檔案,呼叫服務 ```go func main() { conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure()) if err != nil { log.Fatal(err) } defer conn.Close() client := proto.NewHelloServiceClient(conn) reply, err := client.Hello(context.Background(), &proto.String{Value: "world"}) if err != nil { log.Fatal(err) } fmt.Println(reply.GetValue()) } ``` ### 驗證 首先執行服務端 `go run server/server.go`,再執行客戶端 ```sh $ go run client/client.go hello world ``` 這裡只簡單介紹了用法,關於 protobuf 的更多用法可以參考 [https://developers.google.com/protocol-buffers/](https://developers.google.com/protocol-buffers/),關於 protobuf 的原理可以參考 [https://blog.csdn.net/carson_ho/article/details/70568606](https://blog.csdn.net/carson_ho/article/details/70568606),另外 grpc 還支援流式呼叫。 ### 流式呼叫 資料傳輸是流式的,服務端和服務端不用輸完全部資料後才響應,流式傳輸可以是客戶端流式、或服務端流式,也可以同時流式傳輸,只需要一個 stream 標識 ```go service HelloService { rpc Hello (String) returns (String); rpc HelloStream (String) returns (stream String); //服務端流式響應 //rpc HelloStream (stream String) returns (String); //客戶端端流式傳送 //rpc HelloStream (stream String) returns (stream String); //雙向流 } ``` 重新編譯,然後在 `server/server.go` 新增方法 ```go func (HelloServiceImpl) HelloStream(args *proto.String, stream proto.HelloService_HelloStreamServer) error { for i := 0; i < 10; i++ { _ = stream.Send(&proto.String{Value: "hello " + args.GetValue() + strconv.Itoa(i)}) } return nil } ``` 在 `client/client.go` 中呼叫 HelloStream 方法 ```go stream, _ := client.HelloStream(context.Background(), &proto.String{Value: "World"}) for { res, err := stream.Recv() if err == io.EOF { break } if err != nil { log.Printf("stream.Recv: %v", err) } log.Printf("%s", res.String()) } ``` 驗證,首先執行 server,然後執行 client ```sh $ go run server/server.go $ go run client/client.go 2020/07/21 07:41:10 value:"hello World0" 2020/07/21 07:41:10 value:"hello World1" 2020/07/21 07:41:10 value:"hello World2" 2020/07/21 07:41:10 value:"hello World3" 2020/07/21 07:41:10 value:"hello World4" 2020/07/21 07:41:10 value:"hello World5" 2020/07/21 07:41:10 value:"hello World6" 2020/07/21 07:41:10 value:"hello World7" 2020/07/21 07:41:10 value:"hello World8" 2020/07/21 07:41:10 value:"hello Worl