基础篇
基础篇主要是让大家熟悉protoc的一些基本参数,以及常见用法,项目结构如下:
├── proto1
│ ├── greeter
│ │ ├── greeter.proto
│ │ └── greeter_v2.proto
│ └── pb_go
greeter.proto文件内容:
syntax = "proto3";
package greeter;
option go_package="proto1/greeter";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
编译命令如下:
protoc -I . --go_out=. proto1/greeter/greeter.proto
上面的指令可以拆解为三部分,分别对应protoc的三个重要参数,我们首先来看看protoc提供了哪些参数:
$ protoc --help
Usage: protoc [OPTION] PROTO_FILES
-IPATH, --proto_path=PATH 指定搜索路径
--plugin=EXECUTABLE:
....
--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.
--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
@<filename> proto文件的具体位置
1. 搜索路径参数
表示的是我们要在哪个路径下搜索.proto文件,这个参数既可以用-I指定,也可以使用–proto_path=指定
2. 语言插件参数
即上述的--cpp_out=
,--python_out=
等,protoc支持的语言长达13种,且都是比较常见的。像上面出现的语言参数,说明protoc本身已经内置该语言对应的编译插件,我们无需安装。
而如果上面没出现的,比如--go_out=
,就得自己单独安装语言插件,比如--go_out=
对应的是protoc-gen-go
,安装命令如下:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
3. proto文件位置参数
proto文件位置参数即上述的@<filename>
参数,指定了我们proto文件的具体位置,如proto1/greeter/greeter.proto
。
进阶篇
两个易混参数
proto文件(非protoc)有两个易混参数,即package
和xx_package
,xx
指的是你的编译语言,比如你要编程成Go语言,对应的就是go_package
。
package
package
参数针对的是protobuf,是proto文件的命名空间,它的作用是为了避免我们定义的接口,或者message出现冲突。
xx_package
这里以go_package
进行举例说明,该参数主要声明Go代码的存放位置,也可以说它解决的是包名问题(因为proto文件编译后会生成一份.pb.go
文件,既然是go文件,就有包名问题)
.pb.go
常规的存放路径一般是放在同名proto文件下,但也有些人不想这么做,比如他想把所有pb.go
文件都存放在一个特定文件夹下,比如上述的pb_go
目录,那么他有两种办法:
第一种:
# 修改 --go_out,go_package 保持不变
$ protoc --proto_path=. --go_out=./proto1/pb_go proto1/greeter/greeter.proto
这样会在go_out指定的proto1/pb_go目录下再生成go_package指定的proto1/greeter目录,最终的pb.go文件位于此目录下,且文件包名仍然为greeter。
第二种:
# 修改go_package, go_out保持不变
option go_package="proto1/pb_go";
$ protoc --proto_path=. --go_out=. proto1/greeter/greeter.proto
这样直接在proto1/pb_go目录下生成pb.go文件,文件包名为pb_go
go_package
指定的是生成go文件的路径,如果没有显式指定包名(如go_package=“proto1/greeter;packagename”),则包名是路径名的最后一段。而编译时go_out又指定了生成位置,因此最终go文件生成的位置 = go_out指定的目录 + go_package
。
通常情况下,go_package还是写成此protobuf文件所在项目的路径,最终生成的pb.go文件就和此路径相同。
go_out详细解读
大家在使用时,遇到过这些写法:--go_out=paths=import:.
、--go_out=paths=source_relative:.
,或者--go_out=plugins=grpc:.
。
--go_out
参数是用来指定 protoc-gen-go 插件的工作方式和Go代码的生成位置,而上面的写法正是表明该插件的工作方式。
--go_out
主要的两参数为plugins
和paths
,分别表示生成Go代码所使用的插件,以及生成的Go代码的位置。
--go_out
的写法是,参数之间用逗号隔开,最后加上冒号来指定代码的生成位置,比如--go_out=plugins=grpc,paths=import:.
。
paths
参数有两个选项,imports
和 source_relative
, 默认为 import,表示按照生成的Go代码的包的全路径去创建目录层级,source_relative 表示按照 proto源文件的目录层级去创建Go代码的目录层级,如果目录已存在则不用创建。
plugins
参数有不带grpc和带grpc两种(应该还有其它的,目前知道的有这两种),两者的区别如下,带grpc的会多一些跟gRPC相关的代码,实现gRPC通信.
导入其它proto文件
有时候我们可能有多份proto文件,且每份proto文件不一定是完全独立,它们之间会互相引用,这时候该怎么做呢?
└── proto2
├── common.proto
└── greeter
└── greeter.proto
greeter.proto内容如下:
syntax = "proto3";
package greeter;
import "proto2/common.proto";
option go_package="proto2/greeter";
service Greeter {
rpc SayHello (common.Request) returns (common.Response) {}
}
以上述内容为例,假设我们有一个共用的 proto 文件 common.proto,此时 greeter.proto 如果想引用里面的message,就可以使用 import 关键字进行导入。
编译指令如下:
protoc --proto_path=. --go_out=. proto2/greeter/greeter.proto proto2/common.proto
关于protoc-gen-go的多包问题
假设目前的proto文件存放如下:
└── proto
├── common.proto
├── greeter
│ └── greeter.proto
└── user
└── user.proto
如果你想编译所有proto文件(假设生成Go语言),正常的命令应该是这样的:
protoc --proto_path=. --go_out=. proto/*.proto proto/user/*proto proto/greeter/*proto
但是有的朋友可能会想偷懒,想直接这样:
protoc --proto_path=. --go_out=. proto/*.proto
答案是不行的,因为protoc-gen-go不支持这种形式,最终只会编译common.proto。
参考链接:一文了解protoc的使用