Leon's Blogging

Coding blogging for hackers.

Golang Implementation of gRPC

| Comments

最近剛好看到 gRPC 蠻好奇是什麼的,就來了解一下,並試著用 golang 去實現

What are protocol buffers?

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data

  • 簡單的來說,就是類似像 JSONXML 的序列化結構資料格式,但是更小、更快,而且更簡潔
  • 目前支援多種語言 Java, Python, Objective-C, C++, Go, Ruby, and C#
  • 只需要定義一次資料結構,就能自動生成符合你程式語言的檔案,讓你能夠直接在你的程式上使用。
  • 結構就是文件,不需額外撰寫 API 文件,.proto 檔本身就是一種「文件」。

What are gRPC?

  • Simple service definition - 透過 Protocol Buffers 定義 service,就是可以在 .proto 檔案就可以定義好 service 內容
  • Works across languages and platforms - 自動生成支援客戶端的語言,由下圖解說就是,Server跟 Client 端都透過 protocol buffers 來做傳遞,但三個分別是 ruby, java, c++

  • Start quickly and scale
  • Bi-directional streaming and integrated authBi-directional

Quick Start

  • 各種語言都能夠設定,這裡主要先用 Golang quickstart

基本的開發步驟是定義 proto 文件, 定義請求 Request 和 響應 Response 的格式,然後定義一個服務 Service, Service可以包含多個方法。

gRPC requires Go 1.6 or higher.

1
go version

Install gRPC

1
go get -u google.golang.org/grpc

Install Protocol Buffers v3

https://github.com/protocolbuffers/protobuf/releases

  • 根據自己用的 platform,下載編譯好的檔案,並解壓縮
  • bin/protoc binary file 放置在 PATH 底下( ex. $GOPATH/bin)

install the protoc plugin for Go

1
go get -u github.com/golang/protobuf/protoc-gen-go

Try it!

gRPC services are defined in a .proto file, which is used to generate a corresponding .pb.g file.

The .pb.go file is generated by compiling the .proto file using the protocol compiler: protoc.

下載 grpc 時會有一個範例

1
cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

體驗一下,先啟動 sever

1
go run greeter_server/main.go

再跑 client 會發現 response 回來的資料

1
2
3
go run greeter_client/main.go

// Greeting: Hello world

Example

建立資料夾

1
2
3
4
.
├── client   // gRPC 客戶端
├── pb       // 擺放 Protobuf 文件
└── server   // gRPC 伺服器
  • client:gRPC client,用來和伺服器溝通的程式
  • pb: 定義的 Protobuf 文件,也會放置轉化後的 Protobuf 程式
  • server: gRPC server

proto

定義資料格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 撰寫格式是 Proto v3。
syntax = "proto3";
// 生成的程式在 Golang 中將會屬於 `pb` 套件。
package pb;

// Calculator 定義了一個計算用的服務。
service Calculator {
    // Plus 會接收 CalcRequest 資料作加總,最終會回傳 CalcReply。
    rpc Plus (CalcRequest) returns (CalcReply) {}
}

// CalcRequest 包含了兩個數字,將會傳送至計算服務並對兩個數字進行計算。
message CalcRequest {
// 後面的數字是 Protocol Buffers 編碼與解碼所會用到的編號,能夠移除其中一個欄位而不打亂整個資料結構的編碼與解碼(除非更改了數字編號)
    int32 number_a = 1;
    int32 number_b = 2;
}

// CalcReply 是計算結果,將會回傳給客戶端。
message CalcReply {
    int32 result = 1;
}

透過 protoc 產生 .pb.go

1
protoc --go_out=plugins=grpc:. *.proto
  • Generated client and server code.
  • Code for populating, serializing, and retrieving our HelloRequest and HelloReply message types.

目前資料夾

1
2
3
4
5
6
7
8
.
├── client
   └── main.go
├── pb
   ├── calc.pb.go
   └── calc.proto
└── server
    └── main.go

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// server/main.go
package main

import (
  "context"
  "log"
  "net"

  "google.golang.org/grpc"

  "github.com/mgleon08/demo_proto/pb"
)

const (
  port = ":50051"
)

// server 建構體會實作 Calculator 的 gRPC 伺服器。
type server struct{}

// Plus 會將傳入的數字加總。
func (s *server) Plus(ctx context.Context, in *pb.CalcRequest) (*pb.CalcReply, error) {
  // 計算傳入的數字。
  result := in.NumberA + in.NumberB
  // 包裝成 Protobuf 建構體並回傳。
  return &pb.CalcReply{Result: result}, nil
}

func main() {
  // 監聽指定埠口,這樣服務才能在該埠口執行。
  lis, err := net.Listen("tcp", port)
  if err != nil {
      log.Fatalf("無法監聽該 port: %v", err)
  }

  // 建立新 gRPC 伺服器並註冊 Calculator 服務。
  s := grpc.NewServer()
  // RegisterCalculatorServer 是透過 .proto 定義轉換而成
  pb.RegisterCalculatorServer(s, &server{})

  // 開始在指定埠口中服務。
  if err := s.Serve(lis); err != nil {
      log.Fatalf("無法提供服務: %v", err)
  }
}

client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// client/main.go
package main

import (
  "context"
  "log"

  "google.golang.org/grpc"

  "github.com/mgleon08/demo_proto/pb"
)

const (
  address = "localhost:50051"
)

func main() {
  // 建立連線到遠端 gRPC 伺服器。
  conn, err := grpc.Dial(address, grpc.WithInsecure())
  if err != nil {
      log.Fatalf("無法連線: %v", err)
  }
  // 結束後要關閉
  defer conn.Close()

  // 建立新的 Calculator 客戶端,所以等一下就能夠使用 Calculator 的所有方法。
  c := pb.NewCalculatorClient(conn)

  // 傳送新請求到遠端 gRPC 伺服器 Calculator 中,並呼叫 Plus 函式,讓兩個數字相加。
  r, err := c.Plus(context.Background(), &pb.CalcRequest{NumberA: 32, NumberB: 32})
  if err != nil {
      log.Fatalf("無法執行 plus: %v", err)
  }
  log.Printf("回傳結果: %d", r.Result)
}

啟動

1
2
3
4
5
// 先啟動 server
go run ./server/main.go

// 再啟動 client,就會有 response
go run ./client/main.go

demo

go-grpc-demo

Reference:

Comments