相关处理工具主要是 ,基于 C++ 语言实现。

    用户写好 描述文件,之后便可以使用 protoc 自动编译生成众多计算机语言(C++、Java、Python、C#、Go 等)的接口代码。这些代码可以支持 gRPC,也可以不支持。

    gRPC 是 Google 开源的 RPC 框架和库,已支持主流计算机语言。底层通信采用 HTTP2 协议,比较适合互联网场景。gRPC 在设计上考虑了跟 ProtoBuf 的配合使用。

    两者分别解决不同问题,可以配合使用,也可以分开单独使用。

    典型的配合使用场景是,写好 .proto 描述文件定义 RPC 的接口,然后用 protoc(带 gRPC 插件)基于 .proto 模板自动生成客户端和服务端的接口代码。

    需要工具主要包括:

    • 编译工具:,以及一些官方没有带的语言插件;
    • 运行环境:各种语言的 protobuf 库,不同语言有不同的安装来源;

    语法类似 C++ 语言,可以参考 ProtoBuf 语言规范:https://developers.google.com/protocol-buffers/docs/proto。

    比较核心的,message 是代表数据结构(里面可以包括不同类型的成员变量,包括字符串、数字、数组、字典……),service 代表 RPC 接口。变量后面的数字是代表进行二进制编码时候的提示信息,1~15 表示热变量,会用较少的字节来编码。另外,支持导入。

    参考官方给出示例,如下所示:

    编译最关键的参数是输出语言格式参数,例如,python 为 --python_out=OUT_DIR

    一些还没有官方支持的语言,可以通过安装 protoc 对应的 plugin 来支持。例如,对于 Go 语言,可以安装

    1. $ go get -u github.com/golang/protobuf/{protoc-gen-go,proto} // 前者是 plugin;后者是 go 的依赖库

    之后,正常使用 protoc --go_out=./ ./hello.proto 命令调用 protoc-gen-go 插件来生成 hello.pb.go。

    ProtoBuf 提供了 Marshal/Unmarshal 方法来将数据结构进行序列化操作。所生成的二进制文件在存储效率上比 XML 高 3~10 倍,并且处理性能高 1~2 个数量级。

    gRPC

    相关工具主要包括:

    • 运行时库:各种不同语言有不同的安装方法,主流语言的包管理器都已支持。
    • protoc,以及 gRPC 插件和其它插件:采用 ProtoBuf 作为 IDL 时,对 .proto 文件进行编译处理。

    类似其它 RPC 框架,gRPC 的库在服务端提供一个 gRPC Server,客户端的库是 gRPC Stub。典型的场景是客户端发送请求,同步或异步调用服务端的接口。客户端和服务端之间的通信协议是基于 HTTP2 的 协议,支持双工的流式保序消息,性能比较好,同时也很轻。

    采用 ProtoBuf 作为 IDL,则需要定义 service 类型。生成客户端和服务端代码。用户自行实现服务端代码中的调用接口,并且利用客户端代码来发起请求到服务端。一个完整的例子可以参考 https://github.com/grpc/grpc-go/blob/master/examples/helloworld

    1. $ protoc --go_out=plugins=grpc:. hello.proto

    执行后会生成对应的 .pb.go 文件,其中包括了 proto 文件中指定的结构和 protobuf 协议相关转换代码。

    gRPC 更多原理可以参考。

    实现服务端代码

    生成的 .pb.go 文件中,服务端相关的核心代码如下,主要定义了 GreeterServer 接口,用户可以自行修改或编写实现代码。

    用户需要自行实现服务端接口,代码如下。

    比较重要的,创建并启动一个 gRPC 服务的过程:

    • 创建监听套接字:lis, err := net.Listen("tcp", port)
    • 创建服务端:grpc.NewServer()
    • 注册服务:pb.RegisterGreeterServer()
    • 启动服务端:s.Serve(lis)
    1. package main
    2. import (
    3. "context"
    4. "fmt"
    5. "log"
    6. "net"
    7. "google.golang.org/grpc"
    8. pb "hello/hello"
    9. )
    10. const (
    11. port = ":50051"
    12. )
    13. // 这里实现服务端接口中的方法。
    14. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    15. log.Printf("Received: %v\n", in.GetName())
    16. return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
    17. }
    18. // 创建并启动一个 gRPC 服务的过程:创建监听套接字、创建服务端、注册服务、启动服务端。
    19. func main() {
    20. lis, err := net.Listen("tcp", port)
    21. if err != nil {
    22. log.Fatalf("failed to listen: %v", err)
    23. }
    24. s := grpc.NewServer()
    25. pb.RegisterGreeterServer(s, &server{})
    26. fmt.Printf("Starting listen on port: %s", port)
    27. if err := s.Serve(lis); err != nil {
    28. log.Fatalf("failed to serve: %v", err)
    29. }
    30. }

    编译并启动服务端。

    1. $ go run server/server.go
    2. Starting listen on port: :50051

    生成客户端代码

    生成的 Go 文件中客户端相关代码如下,主要和实现了 HelloServiceClient 接口。用户可以通过 gRPC 来直接调用这个接口。

    用户直接调用接口方法:创建连接、创建客户端、调用接口。

    1. package main
    2. import (
    3. "context"
    4. "log"
    5. "os"
    6. "time"
    7. pb "hello/hello"
    8. )
    9. const (
    10. address = "localhost:50051"
    11. defaultName = "world"
    12. )
    13. func main() {
    14. // Set up a connection to the server.
    15. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    16. if err != nil {
    17. log.Fatalf("did not connect: %v", err)
    18. }
    19. defer conn.Close()
    20. c := pb.NewGreeterClient(conn)
    21. // Contact the server and print out its response.
    22. name := defaultName
    23. if len(os.Args) > 1 {
    24. name = os.Args[1]
    25. }
    26. ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    27. defer cancel()
    28. r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    29. if err != nil {
    30. log.Fatalf("could not greet: %v", err)
    31. }
    32. log.Printf("Greeting: %s", r.GetMessage())
    33. }
    1. Greeting: Hello world