Skip to content

使用protoc编译.proto文件

Protoc介绍

protoc是protobuf文件(.proto)的编译器,我们可以借助这个工具把 .proto 文件转译成各种编程语言对应的源码,包含数据类型定义、调用接口等。protoc在设计上把protobuf和不同的语言解耦,底层用c++来实现protobuf结构的存储,然后通过插件的形式来生成不同语言的源码。源码中包含的插件有 csharp、java、js、objectivec、php、python、ruby等多种。

安装针对go的protoc插件

  • protoc本身并不支持Golang,需要额外安装对应的插件。
    • 方法1,使用go install @latest安装
      go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
      go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
      
    • 方法2,在一个已经包含go.mod文件的项目里使用go get
      go get google.golang.org/protobuf/cmd/protoc-gen-go
      go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
      
  • 安装成功后,会在$GOPATH/bin目录下生成两个2进制文件

    1. protoc-gen-go
    2. protoc-gen-go-grpc

    注:这时候执行protoc --go_out=. --go-grpc_out=. *.proto时,可能出现--go_out: protoc-gen-go: Plugin failed with status code 1的报错,这是由于$GOPATH/bin目录没有添加到系统环境变量里。

实践

准备一个.proto文件

  • 新建gRPC项目,使用go mod init初始化,创建pb文件夹,新建hello.proto文件
  • 指定syntax,package,option go_package,分别代表protobuf版本,包名,编译生成的.pb.go文件目录

    syntax = "proto3";
    package pb;
    option go_package = "./proto/hello";
    
    • option go_package = "./proto/hello":代表在protoc编译时,在--go_out指定的目录下自动生成proto/hello文件夹,同时在该文件夹下生成的.pb.go文件的包名是hello
    • 可以通过如下格式指定包名option go_package = "./proto/hello;helloRename";表示生成的.pb.go文件的包名是helloRename

定义service和message

  • service 对应golang中的接口
  • message 对应golang中的结构体

    // 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;
    }
    
    // The response message containing the greetings
    message HelloReply {
        string message = 1;
    }
    

完整的 hello.proto

syntax = "proto3";
package pb;
option go_package = "./proto/hello";

// 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;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

使用protoc编译

  • gRPC/pb目录下使用如下命令进行编译
    protoc --go_out=. --go-grpc_out=. ./hello.proto
    
  • 生成对应的.pb.go_grpc.pb.go两个文件,前者主要是对message生成对应的结构体和方法,后者生成gRPC,主要是对service生成对应的interface和方法
  • go_out=. 指定生成的pb.go文件所在目录(如果没有该目录,需要手动提前创建),.代表当前protoc执行目录,结合.proto文件中的option go_package,其最终的生成文件目录为go_out指定目录/go_package指定目录
  • go-grpc_out针对_grpc.pb.go文件,作用同上。
  • 另外官网文档里还有一个--go_opt=paths=source_relative,其含义代表生成的.pb.go文件路径不依赖于.proto文件中的option go_package配置项,直接在go_out指定的目录下生成.pb.go文件(.pb.go文件的package名还是由option go_package决定)。
  • --go-grpc_opt=paths=source_relative,针对_grpc.pb.go文件,作用同上。

总结

  • 模块github.com/golang/protobufgoogle.golang.org/protobuf取代
  • 当前版本编译时,之前的方法protoc --go_out=plugins=grpc:. *.proto不再使用,转而用protoc --go_out=. --go-grpc_out=. ./hello.proto代替。有如下提示
    protoc --go_out=plugins=grpc:. *.proto
    --go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC