了解 proto
的基本語法後,就可以來用一個程式語言實作 gRPC 服務了,本篇就用 Golang 來作範例吧,練習內容是根據官方文件所提供的教學內容,細節可以到這邊閱讀。
練習一
Golang 環境如何安裝這邊就不說明了。需要安裝 Protobuf Compiler
(安裝連結),如果環境沒有安裝 C++,也可以下載預先 compile 好的執行檔,並設定好環境參數 (步驟說明),如果一切設定正確,在命令視窗內應可執行 protoc
指令了
練習題目: 建立一個 address book,功能是可以從檔案中存取聯絡資訊,聯絡資訊包含姓名、ID、Email、連絡電話
初始化專案
-
建立一個新的資料夾
-
執行
go mod init <project name>
指令 -
建立
addressbook.proto
檔案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
31syntax = "proto3";
package tutorial;
import "google/protobuf/timestamp.proto";
option go_package = "./tutorialpb";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
repeated PhoneNumber phones = 4;
google.protobuf.Timestamp last_updated = 5;
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
}
enum PhoneType {
PHONE_TYPE_UNSPECIFIED = 0;
PHONE_TYPE_MOBILE = 1;
PHONE_TYPE_HOME = 2;
PHONE_TYPE_WORK = 3;
}
message AddressBook {
repeated Person people = 1;
}- 123
-
安裝
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
-
開啟命令視窗,執行
protoc -I=. --go_out . addressbook.proto
指令,即可看到程式碼產生再tutorialpb
的資料夾下
實作
-
建立
main.go
-
引用剛剛產生的程式碼,其內容會包含
- An
AddressBook
structure with aPeople
field. - A
Person
structure with fields forName
,Id
,Email
andPhones
. - A
Person_PhoneNumber
structure, with fields forNumber
andType
. - The type
Person_PhoneType
and a value defined for each value in thePerson.PhoneType
enum.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package main
import (
pb "go-grpc/tutorialpb"
)
func main() {
p := pb.Person{
Id: 1234,
Name: "John Doe",
Email: "[email protected]",
Phones: []*pb.Person_PhoneNumber{
{Number: "555-4321", Type: pb.PhoneType_PHONE_TYPE_HOME},
},
}
} - An
Writing a Message
1 | package main |
Reading a Message
1 | package main |
練習二
練習題目: 建立一個可以回 Hello World 的 gRPC Service
-
安裝 protocol compiler plugins
1
2go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest -
建立
helloWorld.proto
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17syntax = "proto3";
package helloWorld;
option go_package = "./helloWorldpb";
service Greeter {
rpc SayHello(HelloReqeust) returns (HelloResponse){}
}
message HelloResponse {
string message = 1;
}
message HelloReqeust {
string name = 1;
} -
執行
protoc --go_out=. --go-grpc_out=. helloworld.proto
,會產生兩個檔案- *.pb.go* 包含用於 protobuf 消息的序列化/反序列化的程式
- *_grpc.pb.go* 包含 gRPC 服務器和客戶端的程式
-
建立
server/server.go
,建立 gRPC Server 需要完成兩件事情- 實作定義在 proto 檔中的 service interface
- 啟動 gRPC server,設定 service
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
46
47
48package main
import (
"context"
"flag"
"fmt"
"log"
"net"
pb "go-grpc/helloWorldpb"
"google.golang.org/grpc"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// 實作 Greeter gRPC
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloReqeust) (*pb.HelloResponse, error) {
return &pb.HelloResponse{Message: "Hello World " + in.GetName()}, nil
}
func main() {
flag.Parse()
// service port 設定
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 建立一個空的 gRPC Server
s := grpc.NewServer()
// 註冊 Greeter Service
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
// 啟動 gRPC Server
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
} -
建立
client/client.go
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
46package main
import (
"context"
"flag"
"log"
"time"
pb "go-grpc/helloWorldpb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
defaultName = "world"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
name = flag.String("name", defaultName, "Name to greet")
)
func main() {
flag.Parse()
// 建立 gRPC 連線
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
// 建立 Client
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 呼叫 gRPC Service 方法
r, err := c.SayHello(ctx, &pb.HelloReqeust{Name: *name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}