Hello gRPC

按照惯例,这里也从一个Hello项目开始,本项目定义了一个Hello Service,客户端发送包含字符串名字的请求,服务端返回Hello消息。

流程

  1. 编写.proto描述文件
  2. 编译生成.pb.go文件
  3. 服务端实现约定的接口并提供服务
  4. 客户端按照约定调用.pb.go文件中的方法请求服务

环境

下载对应平台的代码生成工具(或者自行编译)

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

安装编译插件

1
2
3
4
5
6
7
8
9
10
11
12
13
# 在gopath的bin目录下生成protoc-gen-go.exe
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc

git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net

git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text

git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto

cd $GOPATH/src/
go install google.golang.org/grpc

实例

1
2
3
4
5
6
7
8
9
|—— hello/    
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello/
|—— hello.proto // proto描述文件
|—— hello.pb.go // proto编译后文件

Step1:编写描述文件:hello.proto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
syntax = "proto3"; // 指定proto版本
package hello; // 指定默认包名

// 指定golang包名
option go_package = "hello";// 定义Hello服务
service Hello { // 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}

// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}

hello.proto文件中定义了一个Hello Service,该服务包含一个SayHello方法,同时声明了HelloRequestHelloResponse消息结构用于请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端,服务端响应HelloResponse消息。一个最简单的服务就定义好了。

Step2:编译生成.pb.go文件

1
2
3
$ cd proto/hello
# 编译hello.proto
$ protoc.exe -I F:/goproject/src/hellogrpc/proto/hello/ --go_out=plugins=grpc:F:/goproject/src/hellogrpc/proto/hello/ hello.proto

在当前目录内生成的hello.pb.go文件,按照.proto文件中的说明,包含服务端接口HelloServer描述,客户端接口及实现HelloClient,及HelloRequestHelloResponse结构体。

注意:不要手动编辑该文件

Step3:实现服务端接口 server/main.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
package main
import (
"fmt"
"net"
pb "hellogrpc/proto/hello" // 引入编译生成的包
"context"
"google.golang.org/grpc"
"log"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)
// 定义helloService并实现约定的接口
type helloService struct{}
// HelloService Hello服务
var HelloService = helloService{}
// SayHello 实现Hello服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {

listen, err := net.Listen("tcp", Address)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
// 实例化grpc Server
s := grpc.NewServer()
// 注册HelloService
pb.RegisterHelloServer(s, HelloService)
log.Println("Listen on " + Address)
s.Serve(listen)
}

服务端引入编译后的proto包,定义一个空结构用于实现约定的接口,接口描述可以查看hello.pb.go文件中的HelloServer接口描述。实例化grpc Server并注册HelloService,开始提供服务。

运行:

1
2
$ go run main.go
Listen on 127.0.0.1:50052 # 服务端已开启并监听50052端口

Step4:实现客户端调用 client/main.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
package main
import (
pb "hellogrpc/proto/hello" // 引入编译生成的包
"golang.org/x/net/context"
"google.golang.org/grpc"
"log"
)
const (
// Address gRPC服务地址
Address = "127.0.0.1:50052"
)
func main() {
// 连接
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := pb.NewHelloClient(conn)
// 调用方法
req := &pb.HelloRequest{Name: "gRPC"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
log.Fatalln(err)
}
log.Println(res.Message)
}

客户端初始化连接后直接调用hello.pb.go中实现的SayHello方法,即可向服务端发起请求,使用姿势就像调用本地方法一样。

运行:

1
2
$ go run main.go
Hello gRPC. # 接收到服务端响应

建议到这里仔细看一看hello.pb.go文件中的内容,对比hello.proto文件,理解protobuf中的定义转换为golang后的结构。