[Go] 用 Go 建立 gRPC 的 Server 與 Client
此篇不會說明有關 gRPC 與 protocol buffers 的詳細概念,如果要瞭解可以到 grpc.io 閱讀相關文件。 今天會筆記,怎麼從無到用 Go 建立好 gRPC 的 Server 與 Client,當作是學習 gRPC 的起點。
安裝
開始之前先把環境建立好,在 Compiling your protocol buffers 文件裡面有詳細說明如何安裝。
1. 首先要先到 protobuf-github release 下載 protobuf compiler 執行檔案。
現在最新的版本是 Protocol Buffers v3.11.4 ,而我電腦是 win64,所以我就選擇 protoc-3.11.4-win64.zip 下載。
2. 將 protobuf compiler 執行檔加入環境變數
將下載的檔案解壓縮後,有 bin 資料夾,將裡面的 protoc.exe 加入環境變數。
如果你的電腦有安裝 Go 的話,可以把檔案放在 ${GOROOT}/bin
底下,以我電腦為例,我就把 protoc.exe 放到 C:\Go\bin
底下,因為 ${GOROOT}/bin
已經加入環境變數了。 完成後打開命令提示字元輸入 $ protoc --version
就可以看到版本號了。
3. 安裝 Go protocol buffers plugin
這是用來產生 Go Code 的工具,請執行下列指令安裝,1
2$ go get google.golang.org/protobuf/cmd/protoc-gen-go
$ go install google.golang.org/protobuf/cmd/protoc-gen-go
安裝好後,就會看到 %{GOPATH}/bin
底下會有 protoc-gen-go.exe
執行檔案。
根據 protocolbuffers/protobuf-go release note 的說明,這是因為 v1.20 以後 protoc-gen-go
不再支援產生 gRPC service definitions,未來要改用 protoc-gen-go-grpc
。
但是我更改使用 protoc-gen-go-grpc
卻又產生 'protoc-gen-go-grpc' is not recognized as an internal or external command 的錯誤訊息。
根據 google.golang.org/grpc: move protoc-gen-go-grpc into grpc module 的說明,這是因為 google.golang.org/protobuf
還沒有把 protoc-gen-go-grpc
的工具從 github.com/golang/protobuf
移植過來,所以要修正這個的問題,我們改從 github.com/golang/protobuf
取得 protoc-gen-go.exe
,安裝方式請執行下面兩行程式。
- bash
1 | $ go get -u github.com/golang/protobuf/protoc-gen-go |
上敘是我在寫此篇文章當下遇到的問題,我相信過不久 Goolge 會把文件跟這個錯誤修正好。
4. 最後要安裝 Golang 的 grpc package ,這是要用來寫 grpc code 的 package
- bash
1 | $ go get -u google.golang.org/grpc |
這樣我們就把環就都弄好啦。接下來進入簡單的概念說明與開始寫 code 了。
概念解說
我從 Mastering Go - Second Edition 這本書擷取一段 gRPC 的說明。
Strictly speaking, gRPC is a protocol built on HTTP/2 that allows you to create services
easily. gRPC can use protocol buffers to specify an interface definition language, as well as
to specify the format of the interchanged messages. gRPC clients and servers can be written
in any programming language without the need to have clients written in the same
programming language as their servers.
上敘的翻譯為,「gRPC 是建立在 HTTP/2 上的通訊協定,gRPC 可以使用 protocol buffers 定義訊息的格式,gRPC 可以使用任何語言實作。」
而我從此課程 Enhancing Application Communication with gRPC 擷取一張圖片,說明 Client 與 Server 使用 protocol buffers 的格式來傳送資料。
根據上圖的架構就是我們這篇文章要做的事情
- 使用 protocol buffers 定義 message type 產生程式碼框架。
- 實作 Server Code,執行 gRPC Server 。
- 實作 Client Code。
定義 Message Type
我們用 Define Message Type 範例來看 Message Type 的長相1
2
3
4
5
6
7syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
這邊要特別說的是為什麼每一個欄位後面會有一個數字呢? 根據 Assigning Field Numbers 的說明
Assigning Field Numbers
As you can see, each field in the message definition has a unique number. These field numbers are used to identify your fields in the message binary format, and should not be changed once your message type is in use. Note that field numbers in the range 1 through 15 take one byte to encode, including the field number and the field’s type (you can find out more about this in Protocol Buffer Encoding). Field numbers in the range 16 through 2047 take two bytes. So you should reserve the numbers 1 through 15 for very frequently occurring message elements. Remember to leave some room for frequently occurring elements that might be added in the future.
這是說,當資料傳送的時候是用 message binary format ,該數字是用來 定位 該欄位。 而數字1-15 是只用了 1 byte encode, 16-2047 用了 2 btyes encode ,所以建議常用的欄位可以放在前 15 個欄位,可以多少減少一些資料的傳送量。
為了今天的 Demo 能夠簡單,我將使用 Message Definition 提供的範例,當作是我們這篇的範例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18syntax = "proto3";
option go_package = "pb";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
上敘 Message Definition 非常簡單,就定義兩個 Message Type 一個是 HelloRequest
一個是 HelloReply
,然後還有定義一個 RPC 的服務 rpc SayHello (HelloRequest) returns (HelloReply) {}
,而等等的實作程式碼,就是要實作 SayHello
這個方法。
定義好 Message 之後,我們就使用指令 $ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/message.proto
產生程式碼,如下範例,
- bash
1 | $ protoc -I pb/ pb/messages.proto --go_out=plugins=grpc:pb |
也可以使用另一種寫法 $ protoc --go_out=plugins=grpc:$DST_DIR $SRC_DIR
,例如 $ protoc --go_out=plugins=grpc:. pb/*.proto
,參考:gRPC Support。
執行完後,就會看到有一份 messages.pb.go
的程式碼被產生,這就是我們定義好的訊息介面,等等會要用到這份程式碼來實作 Server Code 與 Client Code。
實作 Server Code
我們打開 messages.pb.go
,裡面有一個 interface 如下,這是我們要實作 Server Code 的方法。1
2
3
4
5// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}
所以這邊我們定義一個 MessageService ,實作 GreeterServer interface 的內容。 我的實作內容只是讀取 HellowRequest 的資訊,加一些訊息後回傳給 Client。1
2
3
4
5
6type MessageService struct{
}
func (m MessageService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Server say hello to " + in.GetName()}, nil
}
建立 Server
接下來請參考下方的程式碼,我們要把 MessageService
註冊到 gRPC 的 Server ,並且將 gRPC 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
26package main
import (
"google.golang.org/grpc"
"log"
"net"
"trygrpc/pb"
"trygrpc/service"
)
const (
port = ":50051"
)
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.RegisterGreeterServer(s, &service.MessageService{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
這樣我們 Server Code 就定義好了!!!
實作 Client Code
一樣我們打開 messages.pb.go
,尋找下面那段程式碼,就會發現 Client 的程式碼我們不用自己實作,他已經幫我們產生好了!!! 我們只需要直接用。
- go
1 | type GreeterClient interface { |
而使用方式如下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
34package main
import (
"context"
"google.golang.org/grpc"
"log"
"time"
"trygrpc/pb"
)
const (
address = "localhost:50051"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := "Miles"
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
這樣 Client Code 也完成了!!!
執行程式碼
接下來就先把 Server 先跑起來,然後再執行 Client。 確定跑起來後,我們就完成最簡單的 gRPC 的範例的!!
最後我有把我的範例程式上傳到 Gihub 上,可以參考 MilesLin/gRPC-helloworld
官方提供的程式碼範例
今天範例是來自官方,可以從這邊找到 grpc-Build the example。 簡單的說我們只要安裝好 go get -u google.golang.org/grpc
的 package,就可以在 $GOPATH/src/google.golang.org/grpc/examples/helloworld
資料夾底下找到各種 grpc 的範例。 而這些範例也有放在 github 上 grpc-go/examples/features/。
延伸閱讀
[grpc.io]
[What are protocol buffers]
[Protocol Buffers]
[Enhancing Application Communication with gRPC]
[Go Quick Start]
[gRPC Concepts]
[grpc-go/examples]