gRPC是由google开发,是一款跨平台,跨语言,开源的远程过程调用(RPC)系统。Docker v1.12.3就是通过gRPC调用containerd的。所以本次笔记介绍基于官方例子改写的一个例子,分别用Go和Python实现。

环境搭建

略(由于google.golang.org被墙,所以我是通过containerd源码包中的src来解决依赖的,不方便)。

proto

gRPC的序列化由protobuf完成。我们先定义helloworld.proto文件,里面定义了Greeter service, HelloRequest message和HelloReply。其中HelloRequest message定义了普通变量,enum变量,数组变量及oneof和any变量。

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
syntax = "proto3";
option java_package = "io.grpc.examples";
package helloworld;
import "google/protobuf/any.proto";
// The greeter 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;
enum Gender {
FEMALE = 0;
MALE = 1;
}
Gender gender = 2;
repeated string hobbies = 3;
oneof office {
string school = 4;
string company = 5;
}
map<string, string> phone = 6;
google.protobuf.Any other = 7;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}

使用protoc --go_out=plugins=grpc:. helloworld.proto可生成helloworld.pb.go文件,即Go语言需要调用的文件。
使用python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. helloworld.proto可生成helloworld_pb2_grpc.py和helloworld_pb2.py文件,即Python语言需要调用的文件。

Go

server.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
package main
// server.go
import (
"log"
"fmt"
"net"
"golang.org/x/net/context"
"google.golang.org/grpc"
pb "helloworld/helloworld"
)
const (
port = ":50051"
)
type server struct {}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
name := fmt.Sprintf("%q", in.GetName())
hobbies := fmt.Sprintf("%q", in.GetHobbies())
gender := fmt.Sprintf("%q", in.GetGender())
office := fmt.Sprintf("%q", in.GetOffice())
phone := fmt.Sprintf("%q", in.GetPhone())
other := fmt.Sprintf("%q", in.GetOther().GetValue())
return &pb.HelloReply{Message: "Name: " + name + " , Gender: " + gender + " , Hobbies: " + hobbies + " , Office: " + office + ", Phone: " + phone + " , Other: " + other}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatal("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
s.Serve(lis)
}

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
package main
//client.go
import (
"log"
"os"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/golang/protobuf/ptypes/any"
pb "helloworld/helloworld"
)
const (
address = "127.0.0.1:50051"
defaultName = "Jack"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatal("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := defaultName
if len(os.Args) >1 {
name = os.Args[1]
}
gender := pb.HelloRequest_MALE
hobbies := []string{"swimming", "running", "football"}
office := &pb.HelloRequest_School{"Fudan University"}
phone := map[string]string{"telephphone": "13900010001", "phone": "123456"}
other := &any.Any{Value: []byte("This is my greet!")}
request := pb.HelloRequest{Name: name, Gender: gender, Hobbies: hobbies, Office: office, Phone: phone, Other: other}
r, err := c.SayHello(context.Background(), &request)
if err != nil {
log.Fatal("could not greet: %v", err)
}
log.Printf("Greeting from: %s", r.Message)
}

运行go run server.gogo run client.go,在client端,可得到:

1
2017/11/11 15:57:23 Greeting from: Name: "Jack" , Gender: "MALE" , Hobbies: ["swimming" "running" "football"] , Office: &{"Fudan University"}, Phone: map["phone":"123456" "telephphone":"13900010001"] , Other: "This is my greet!"

Python

server.py:

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
"""The Python implementation of the GRPC helloworld.Greeter server."""
from concurrent import futures
import time
import string
import grpc
from helloworld import helloworld_pb2
from helloworld import helloworld_pb2_grpc
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
class Greeter(helloworld_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
name = request.name
hobbies = request.hobbies
gender = ""
if request.gender == helloworld_pb2.HelloRequest.MALE:
gender = "MALE"
else:
gender = "FEMALE"
office = request.school
phone = request.phone
other = request.other.value
message = "Name: " + name + " , Gender: " + gender + " , Hobbies: " + str(hobbies) + " , Office: " + office + ", Phone: " + str(phone.items()) + " , Other: " + other
return helloworld_pb2.HelloReply(message=message)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
server.add_insecure_port('[::]:50051')
server.start()
try:
while True:
time.sleep(_ONE_DAY_IN_SECONDS)
except KeyboardInterrupt:
server.stop(0)
if __name__ == '__main__':
serve()

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from __future__ import print_function
import grpc
from helloworld import helloworld_pb2
from helloworld import helloworld_pb2_grpc
from google.protobuf import any_pb2
def run():
channel = grpc.insecure_channel('127.0.0.1:50051')
stub = helloworld_pb2_grpc.GreeterStub(channel)
name = "Jack"
gender = "MALE"
hobbies = ["swimming", "running", "football"]
school = "Fudan"
phone = {"telephphone": "13900010001", "phone": "123456"}
other = any_pb2.Any(value="This is my greet!")
request = helloworld_pb2.HelloRequest(name=name, gender=gender, hobbies=hobbies, school=school, phone=phone, other=other)
response = stub.SayHello(request)
print(response.message)
if __name__ == '__main__':
run()

运行python server.gopython client.go,在client端,可得到:

1
Name: Jack , Gender: MALE , Hobbies: [u'swimming', u'running', u'football'] , Office: Fudan, Phone: [(u'phone', u'123456'), (u'telephphone', u'13900010001')] , Other: This is my greet!

总结

protobuf的语法网上有很多资源。需要着重说明的是,上面的server.py和client.go也可以得到正确的结果,这充分说明了gRPC是跨语言的,很神奇。