gRPC-Go基础(1)protoc的使用

发布时间:2023年12月26日

1. 简介

前面说过,grpc使用Protocol Buffer(简称protobuf)作为接口描述语言,protobuf的重点之一即是protoc工具的使用,这篇文章将介绍protoc工具的使用,特别是和go相关插件的组合使用。

2.protoc命令

我们首先构建一个项目,项目结构如下:

proto-project
├── cient.go
├── genproto
├── proto
│   ├── messagepb
│   └── simplepb
│       └── simple.proto
└── server.go

其中,simple.proto的内容如下:

syntax = "proto3";

package simplepb;

option go_package = "github.com/IguoChan/proto-project/genproto/simplepb";

message SimpleRequest{
  string data = 1;
}

message SimpleResponse{
  int32 code = 1;
  string value = 2;
}

// 定义我们的服务(可定义多个服务,每个服务可定义多个接口)
service Simple{
  rpc Route (SimpleRequest) returns (SimpleResponse){};
}

我们希望最后生成的go文件出现在genproto文件夹中,我们在项目根目录下执行以下指令:

protoc -I./proto --go_out=./genproto --go_opt paths=source_relative ./proto/*/*.proto

从上面可以看出,protoc命令有三个重要的参数,分别是搜索路径参数、语言插件参数和文件位置参数,以下将分别介绍一下这三种参数。
2.1 搜索路径参数

$ protoc --help
Usage: protoc [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given:
  -IPATH, --proto_path=PATH   Specify the directory in which to search for
                              imports.  May be specified multiple times;
                              directories will be searched in order.  If not
                              given, the current working directory is used.
                              If not found in any of the these directories,
                              the --descriptor_set_in descriptors will be
                              checked for required proto file.

我们通过这个参数来设置需要导入的proto文件位置,如果没设置,那就以当前工作目录为准去寻找,通常写作-IPATH或者--proto_path=PATH

2.2 文件位置参数
最后就是文件位置参数,文件位置参数的描述如下:

 @<filename>                 Read options and filenames from file. If a
                              relative file path is specified, the file
                              will be searched in the working directory.
                              The --proto_path option will not affect how
                              this argument file is searched. Content of
                              the file will be expanded in the position of
                              @<filename> as in the argument list. Note
                              that shell expansion is not applied to the
                              content of the file (i.e., you cannot use
                              quotes, wildcards, escapes, commands, etc.).
                              Each line corresponds to a single argument,
                              even if it contains spaces.

按照其中的描述,proto_path,也就是搜索路径参数对其并无影响,但是实际上并不是的,比如我们在messagepb文件夹新增一个message.proto文件,其引用了simple.proto,如下:

syntax = "proto3";

package messagepb;

option go_package = "github.com/IguoChan/proto-project/genproto/messagepb";

import "simplepb/simple.proto";

message MessageRequest {
  simplepb.SimpleRequest req = 1;
}

在项目的根目录下,执行以下指令,可以生成正确的go代码:

$ pwd             
/Users/xxx/workspace/proto-project
$ protoc  -I./proto   --go_out=./genproto  --go_opt paths=source_relative ./proto/messagepb/message.proto

此时整个项目如下,说明message.pb.go文件生成的是正确的.

proto-project
├── client.go
├── genproto
│   └── messagepb
│       └── message.pb.go
├── go.mod
├── go.sum
├── proto
│   ├── messagepb
│   │   └── message.proto
│   └── simplepb
│       └── simple.proto
└── server.go


但是如果我们到以下目录执行如下指令时,就会发现会报错:

$ pwd   
/Users/xxx/workspace/proto-project/proto/messagepb
$ protoc  -I../   --go_out=../../genproto  --go_opt paths=source_relative ./message.proto
./message.proto: File does not reside within any path specified using --proto_path (or -I).  You must specify a --proto_path which encompasses this file.  Note that the proto_path must be an exact prefix of the .proto file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it's harder than you think).


最开始时,我并不理解这两种方式有什么区别,通过万能的互联网也没有找到特别让人信服的说法,比如以下两种:

  1. protobuf编译出错…给出的结论是如果指定了proto_path(-I)参数,protoc不在当前目录寻找proto文件了。根据这个思路,我们将指令改为以下方式,发现会报错,即找不到目标文件的位置。说明以上说法也不是完全准确。
$ protoc  -I../   --go_out=../../genproto  --go_opt paths=source_relative ./messagepb/message.proto     
Could not make proto path relative: ./messagepb/message.proto: No such file or directory

  1. File does not reside…中,有人认为,-I参数中不应该存在…/的形式,我认为描述也不准确。
    经过我的不断实验,发现在此目录下,有以下几种方式都是能够正确产生go文件的:
$ pwd   
/Users/xxx/workspace/proto-project/proto/messagepb
# 1.文件位置参数中必须包含-I中的参数
$ protoc  -I../   --go_out=../../genproto  --go_opt paths=source_relative ../messagepb/message.proto
# 2.绝对路径时也必须满足“文件位置参数中必须包含-I中的参数”
protoc  -I/Users/xxx/workspace/proto-project/genproto/../proto/messagepb/../   --go_out=/Users/xxx/workspace/proto-project/proto/messagepb/../../genproto  --go_opt paths=source_relative /Users/xxx/workspace/proto-project/genproto/../proto/messagepb/../messagepb/message.proto
# 3.以下这种形式也可以
protoc  -I../   --go_out=../../genproto  --go_opt paths=source_relative messagepb/message.proto

实验总结有以下规律,但是没有从其他资料得到验证(也没有时间去扒拉源码啦),如果有问题还请大家指正!

  1. 如果文件位置参数以…/、./或者/开头:1)首先判断文件位置参数是否包含-I中的位置参数,比如以上第一种情况里…/messagepb/message.proto中包含-I…/中的…/,第二种情况里/Users/xxx/workspace/proto-project/genproto/…/proto/messagepb/…/messagepb/message.proto中包含-I参数中的/Users/xxx/workspace/proto-project/genproto/…/proto/messagepb/…/;2)判断这个路径是否真实存在所需proto文件;当1)和2)两个条件都为是,才能找到正确的文件位置参数。
  2. 如果文件参数以正常文件或者文件夹开头(也就是字母等):将-I参数添加到文件前,比如以上第三种情况中,组合的path为…/messagepb/message.proto;将其作为新的文件位置参数输入到1中,再判断条件2)。

多个-I参数时,有满足以上条件的即可。至于protoc --help中给出的-I参数对文件位置参数不影响,我觉得描述有误。
2.3 语言插件参数
protoc自带了一些语言插件,如下:

  --cpp_out=OUT_DIR           Generate C++ header and source.
  --csharp_out=OUT_DIR        Generate C# source file.
  --java_out=OUT_DIR          Generate Java source file.
  --js_out=OUT_DIR            Generate JavaScript source.
  --kotlin_out=OUT_DIR        Generate Kotlin file.
  --objc_out=OUT_DIR          Generate Objective-C header and source.
  --php_out=OUT_DIR           Generate PHP source file.
  --python_out=OUT_DIR        Generate Python source file.
  --ruby_out=OUT_DIR          Generate Ruby source file.

但是其并不包括go的语言插件,所以我们需要自己安装,Go Generated Code。其实安装教程我们在前一章已经描述过了,下面重点讲一下文件的输出位置。

3.文件输出位置

我们执行以下指令,可以在genproto文件夹下生成了我们希望的文件输出位置。

$ pwd             
/Users/xxx/workspace/proto-project
$ protoc  -I./proto   --go_out=./genproto  --go_opt paths=source_relative ./proto/*/*.proto
$ tree genproto  
genproto
├── messagepb
│   └── message.pb.go
└── simplepb
    └── simple.pb.go

那么如何控制生成go文件的输出位置呢?官方文档中有描述,protoc通过–go_out参数和–go_opt参数共同决定:

If the paths=import flag is specified, the output file is placed in a directory named after the Go package’s import path. For example, an input file protos/buzz.proto with a Go import path of example.com/project/protos/fizz results in an output file at example.com/project/protos/fizz/buzz.pb.go. This is the default output mode if a paths flag is not specified.
If the paths=source_relative flag is specified, the output file is placed in the same relative directory as the input file. For example, an input file protos/buzz.proto results in an output file at protos/buzz.pb.go.

paths默认为import,在这种模式下,其会在–go_out所指定的目录下完全按照go package(下章介绍)的路径生成文件,比如执行以下命令:

$ pwd             
/Users/xxx/workspace/proto-project
$ protoc  -I./proto   --go_out=./genproto  ./proto/*/*.proto
$ tree genproto
genproto
└── github.com
    └── IguoChan
        └── proto-project
            └── genproto
                ├── messagepb
                │   └── message.pb.go
                └── simplepb
                    └── simple.pb.go

可以发现,其在genproto文件夹下生成了完全按照.proto文件中option go_package所示的文件路径的格式。说实话,这种方式对于工程应用很不友好,比如正常工程的go package地址常带着git仓库的头,而git仓库并不是工程名的一部分,导致很不好管理,所以我们一般使用relative的生成方式。
当使用–go_opt paths=source_relative指定生成的文件位置时,其生成规则是.proto文件相对于-I参数的相对位置,即是生成的.pb.go文件相对–go_out参数的相对位置,所以以下指令才能生成如下的工程文件。

$ pwd             
/Users/xxx/workspace/proto-project
$ protoc  -I./proto   --go_out=./genproto  --go_opt paths=source_relative ./proto/*/*.proto
$ tree ../proto-project                                                               
../proto-project
├── client.go
├── genproto
│   ├── messagepb
│   │   └── message.pb.go
│   └── simplepb
│       └── simple.pb.go
├── go.mod
├── go.sum
├── proto
│   ├── messagepb
│   │   └── message.proto
│   └── simplepb
│       └── simple.proto
└── server.go

4. Packages

Packages,为了生成go代码,go包是必须要指定的,在protoc中,有两种方式指定go的包:

  1. 在.proto文件中指定
  2. 在protoc下发指令时指定

官方建议在.proto文件中声明它,以便文件的Go包可以用.proto文件本身集中标识,并简化调用时传递的标志集protoc。
需要明确的是,.proto文件中的package和指定go包的go_package没有任何关系。
package参数针对的是protobuf,是proto文件的命名空间,它的作用是为了避免我们定义的接口,或者message出现冲突。
go_package参数主要声明Go代码的存放位置,也可以说它解决的是包名问题。

文章来源:https://blog.csdn.net/ldxxxxll/article/details/135229907
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。