「连载二」gRPC Client and Server

    • google.golang.org/grpc
    • github.com/golang/protobuf/protoc-gen-go

    安装

    Protocol Buffers v3

    1. unzip protobuf-all-3.5.1.zip
    2. cd protobuf-3.5.1/
    3. ./configure
    4. make
    5. make install

    检查是否安装成功

    1. protoc --version

    若出现以下错误,执行 ldconfig 命名就能解决这问题

    1. protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such file or directory

    Protoc Plugin

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

    安装环境若有问题,可参考我先前的文章 内有详细介绍,不再赘述

    本小节开始正式编写 gRPC 相关的程序,一起上车吧 😄

    目录结构

    IDL

    编写

    1. syntax = "proto3";
    2. package proto;
    3. service SearchService {
    4. rpc Search(SearchRequest) returns (SearchResponse) {}
    5. }
    6. message SearchRequest {
    7. string request = 1;
    8. }
    9. message SearchResponse {
    10. string response = 1;
    11. }

    生成

    在 proto 文件夹下执行如下命令:

    1. $ protoc --go_out=plugins=grpc:. *.proto
    • plugins=plugin1+plugin2:指定要加载的子插件列表

    我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)

    • –go_out=.:设置 Go 代码输出的目录

    该指令会加载 protoc-gen-go 插件达到生成 Go 代码的目的,生成的文件以 .pb.go 为文件后缀

    • : (冒号)

    冒号充当分隔符的作用,后跟所需要的参数集。如果这处不涉及 RPC,命令可简化为:

      注:建议你看看两条命令生成的 .pb.go 文件,分别有什么区别

      生成后

      1. type SearchRequest struct {
      2. Request string `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"`
      3. XXX_NoUnkeyedLiteral struct{} `json:"-"`
      4. XXX_unrecognized []byte `json:"-"`
      5. XXX_sizecache int32 `json:"-"`
      6. }
      7. func (m *SearchRequest) Reset() { *m = SearchRequest{} }
      8. func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
      9. func (*SearchRequest) ProtoMessage() {}
      10. func (*SearchRequest) Descriptor() ([]byte, []int) {
      11. }
      12. func (m *SearchRequest) GetRequest() string {
      13. if m != nil {
      14. return m.Request
      15. }
      16. return ""
      17. }

      通过阅读这一部分代码,可以知道主要涉及如下方面:

      • 字段名称从小写下划线转换为大写驼峰模式(字段导出)
      • 生成一组 Getters 方法,能便于处理一些空指针取值的情况
      • ProtoMessage 方法实现 proto.Message 的接口
      • 生成 Rest 方法,便于将 Protobuf 结构体恢复为零值
      • Repeated 转换为切片

      而这一部分代码主要是围绕 fileDescriptor 进行,在这里 fileDescriptor_search_8b45f79ee13ff6a3 表示一个编译后的 proto 文件,而每一个方法都包含 Descriptor 方法,代表着这一个方法在 fileDescriptor 中具体的 Message Field

      这一小节将编写 gRPC Server 的基础模板,完成一个方法的调用。对 server.go 写入如下内容:

      1. package main
      2. import (
      3. "context"
      4. "log"
      5. "net"
      6. "google.golang.org/grpc"
      7. pb "github.com/EDDYCJY/go-grpc-example/proto"
      8. )
      9. type SearchService struct{}
      10. func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
      11. return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil
      12. }
      13. const PORT = "9001"
      14. func main() {
      15. server := grpc.NewServer()
      16. pb.RegisterSearchServiceServer(server, &SearchService{})
      17. lis, err := net.Listen("tcp", ":"+PORT)
      18. log.Fatalf("net.Listen err: %v", err)
      19. }
      20. server.Serve(lis)
      21. }
      • 创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象
      • 将 SearchService(其包含需要被调用的服务端接口)注册到 gRPC Server 的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理
      • 创建 Listen,监听 TCP 端口
      • gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStop

      Client

      接下来编写 gRPC Go Client 的基础模板,打开 client/client.go 文件,写入以下内容:

      1. package main
      2. import (
      3. "context"
      4. "log"
      5. "google.golang.org/grpc"
      6. pb "github.com/EDDYCJY/go-grpc-example/proto"
      7. )
      8. const PORT = "9001"
      9. func main() {
      10. conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
      11. if err != nil {
      12. log.Fatalf("grpc.Dial err: %v", err)
      13. }
      14. defer conn.Close()
      15. client := pb.NewSearchServiceClient(conn)
      16. resp, err := client.Search(context.Background(), &pb.SearchRequest{
      17. Request: "gRPC",
      18. })
      19. if err != nil {
      20. log.Fatalf("client.Search err: %v", err)
      21. }
      22. log.Printf("resp: %s", resp.GetResponse())
      23. }
      • 创建与给定目标(服务端)的连接交互
      • 创建 SearchService 的客户端对象
      • 发送 RPC 请求,等待同步响应,得到回调后返回响应结果
      • 输出响应结果

      验证

      启动 Server

      1. $ pwd
      2. $GOPATH/github.com/EDDYCJY/go-grpc-example
      3. $ go run server.go
      1. $ pwd
      2. $GOPATH/github.com/EDDYCJY/go-grpc-example/client
      3. $ go run client.go
      4. 2018/09/23 11:06:23 resp: gRPC Server

      在本章节,我们对 Protobuf、gRPC Client/Server 分别都进行了介绍。希望你结合文中讲述内容再写一个 Demo 进行深入了解,肯定会更棒 🤔

      参考

      本系列示例代码