ProtoBuf 与 gRPC 你须要知道的知识

ProtoBuf 是一套接口描述语言(IDL)和相关工具集(主要是 protoc,基于 C++ 实现),相似 Apache 的 Thrift)。用户写好 .proto 描述文件,以后使用 protoc 能够很容易编译成众多计算机语言(C++、Java、Python、C#、Golang 等)的接口代码。这些代码能够支持 gRPC,也能够不支持。python

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

二者分别解决的不一样问题,能够配合使用,也能够分开。github

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

ProtoBuf

须要工具主要包括:apache

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

语法相似 C++ 语言,能够参考 语言规范数组

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

默认全部变量都是可选的(optional),repeated 则表示数组。主要 service rpc 接口只能接受单个 message 参数,返回单个 message;框架

syntax = "proto3";
package hello;

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
  repeated int32 number=4;
}

service HelloService {
  rpc SayHello(HelloRequest) returns (HelloResponse){}
}

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

一些尚未官方支持的语言,能够经过安装 protoc 对应的 plugin 来支持。例如,对于 go 语言,能够安装tcp

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

以后,正常使用 protoc --go_out=./ hello.proto 来生成 hello.pb.go,会自动调用 protoc-gen-go 插件。

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

gRPC

工具主要包括:

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

官方文档 写的挺全面了。

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

采用 ProtoBuf 做为 IDL,则须要定义 service 类型。生成客户端和服务端代码。用户自行实现服务端代码中的调用接口,而且利用客户端代码来发起请求到服务端。一个完整的例子能够参考 这里

以上面 proto 文件为例,须要执行时添加 grpc 的 plugin:

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

生成服务端代码

服务端相关代码以下,主要定义了 HelloServiceServer 接口,用户能够自行编写实现代码。

type HelloServiceServer interface {
        SayHello(context.Context, *HelloRequest) (*HelloResponse, error)
}

func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
        s.RegisterService(&_HelloService_serviceDesc, srv)
}

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

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

  • 建立监听套接字:lis, err := net.Listen("tcp", port)
  • 建立服务端:grpc.NewServer()
  • 注册服务:pb.RegisterHelloServiceServer()
  • 启动服务端:s.Serve(lis)
type server struct{}

// 这里实现服务端接口中的方法。
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

// 建立并启动一个 gRPC 服务的过程:建立监听套接字、建立服务端、注册服务、启动服务端。
func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterHelloServiceServer(s, &server{})
    s.Serve(lis)
}

编译并启动服务端。

生成客户端代码

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

type HelloServiceClient interface {
        SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error)
}

type helloServiceClient struct {
        cc *grpc.ClientConn
}

func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
        return &helloServiceClient{cc}
}

func (c *helloServiceClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloResponse, error) {
        out := new(HelloResponse)
        err := grpc.Invoke(ctx, "/hello.HelloService/SayHello", in, out, c.cc, opts...)
        if err != nil {
                return nil, err
        }
        return out, nil
}

用户直接调用接口方法:建立链接、建立客户端、调用接口。

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewHelloServiceClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

编译并启动客户端,查看到服务端返回的消息。

转载请注明:http://blog.csdn.net/yeasy/article/details/52190007