以下是学习笔记目录:
文章目录
protobuf 简介
微服务很流行,go语言也很流行,这两者加起来的grpc服务也很流行。其中 grpc 使用的是ProtoBuf 作为内容传输中间件,看下它的说明:
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法
它对比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
看样子确实很厉害!
0. protobuf 安装
protobuf 的安装很简单,如果是Mac的话,一条命令就搞定了:
brew install protobuf
如果是其他系统,请参考官方文档,也很简单。
安装好的服务,命令执行的关键字是 protoc xx xx xx
。
首先,这是我的例子的目录结构:
/Users/smallyang/www/gowww/go-grpc-example
├── client
├── go.mod
├── go.sum
├── main.go
├── proto
│ ├── hello.proto
│ └── server.proto
└── server.go
其中proto
是我用来存放*.proto
文件的文件夹目录名。
1. -proto_path (-I)选项
protoc --proto_path=proto --go_out=. proto/hello.proto
--proto_path
可以简写为-I
,表示读取*.proto文件的目录
, 可以是绝对路径,也可是是相对路径,如果这个选项不指定,那么就会读取protoc
命令执行时候的当前的目录。注意=左右都不能有空格
比如:
protoc -I=/Users/smallyang/www/gowww/go-grpc-example/proto --go_out=. hello.proto
这个极端的例子,指定的读取的proto目录的路径为:/Users/smallyang/www/gowww/go-grpc-example/proto
当然,一般我们不会这么变态和繁琐,我们一般是先进入项目的目录中去执行protoc生成
:
cd /Users/smallyang/www/gowww/go-grpc-example/
protoc -I=proto --go_out=. hello.proto
或者,我们更进一步,去掉这个-I
选项,我们直接再进入proto
文件夹中,去执行里面的文件:
cd /Users/smallyang/www/gowww/go-grpc-example/
cd proto
protoc --go_out=. hello.proto
那么这样就读取的是/proto
这个文件夹下得proto文件。
所以,最佳实践就是去掉这个-I
选项,直接进入proto的文件夹
protoc --go_out=. hello.proto
2. --go_out 选项
--go_out
表示将*.ptoto文件
转换为golang语言
,是一个特定的选项,它包含2部分,前半部分是用转换成哪个语言,go
表示转换为go语言
,如果是其他语言,也可以是:
--php_out
--csharp_out
--java_out
--js_out
....
但是,很奇葩的是,protobuf 的源码服务里面,唯独没有对go语言的支持,黑人问号???
点进去看,源码里主流语言都有,就是没有golang (是1个单独的项目) :
https://github.com/protocolbuffers/protobuf/tree/2db0e9812a8774f5266370b7a6eae4e4e5a2cdd0#protobuf-runtime-installation
比如你要使用--go_out
,那么就要下载和按照对应的生成go
的服务文件。
所幸的是,下载和使用很简单,执行下面这个命令就可以了:
go get -u github.com/golang/protobuf/protoc-gen-go
它会在$GOPATH/bin
目录下生成1个可执行的protoc-gen-go
文件。所以,这个文件不要删了。不然你使用--go_out
时,会找不到protoc-gen-go
,提示报错。
PS 我测试了一下,生成其他语言文件,是可以直接使用的,就go语言需要单独下载和安装生成服务
➜ proto git:(master) ✗ protoc --php_out=:. hello.proto
➜ proto git:(master) ✗ protoc --js_out=:. hello.proto
➜ proto git:(master) ✗ protoc --ruby_out=:. hello.proto
➜ proto git:(master) ✗ protoc --python_out=:. hello.proto
➜ proto git:(master) ✗ protoc --csharp_out=:. hello.proto
如果源码里有这个语言,但是执行的时候,报错,可能是需要手动安装服务,具体,可以查看对应的语言文档。
我们继续用--go_out
来举例说明:
--go_out=path
=
后半部分,表示的是转换生成后的pb.go
文件,放在哪个文件夹下。注意=左右都不能有空格
同样,他可以是绝对路径,也可以相对路径。比如:
protoc --go_out=/Users/smallyang/www/gowww/go-grpc-example/proto hello.proto
或者相对路径:
protoc --go_out=../client hello.proto
当然,我们一般也不会这么恶心和繁琐,一般是结合-I
选项来弄,-I
我们一般会省略掉,直接是进入proto
文件夹目录执行,那么--go_out
也是直接生成在当前proto
文件夹中:
protoc --go_out=:. hello.proto
或者
protoc --go_out=. hello.proto
至于=:.
和 =.
有啥区别呢? 因为--go_out
他既可以指定生成目录,又能指定使用啥插件,这个在接下来生成grpc的时候,我再讲。但是他们都表示的是将生成的pb.go
文件放入当前的目录下。
所以,最佳实践就是直接将pb.go文件生成在当前proto文件夹中:
--go_out=:.
3. 指定哪个proto源头文件
前面的例子,我们用的hello.proto
文件作为执行源头文件
看个例子:
protoc --go_out=:. ./hello.proto
protoc --go_out=:. hello.proto
上面2个是等价的,都是读取当前目录下得hello.proto文件
来个变态的:
protoc --go_out=:. ./b/c/hello.proto
protoc --go_out=:. b/c/hello.proto
他们表示读取的是:proto/b/c/hello.proto
文件
再结合-I
,来个更变态的:
protoc -I=proto --go_out=:. b/c/hello.proto
这个目录就是2部分结合起来了:proto + b/c/hello.proto = /proto/b/c/hello.proto
protoc -I=proto --go_out=:. hello.proto
这个目录就是2部分结合起来了:proto + hello.proto = /proto/hello.proto
但是,下面这个,就真的看不懂了:
protoc -I=proto --go_out=:. proto/hello.proto
我已经指定了-I
目录,为啥我这样写:proto/hello.proto
还是成功的。结合上面,不应该目录变成:proto/proto/hello.proto
吗?应该报个错,说找不到文件啊。
难道这样写,读取的是绝对路径???
那我就写死绝对路径看看:
protoc -I=proto --go_out=:. /Users/smallyang/www/gowww/go-grpc-example/proto/hello.proto
结果报错了:
/Users/smallyang/www/gowww/go-grpc-example/proto/hello.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).
意思的是在 --proto_path
里找不到这个绝对路径的地址。所以,这个地方,不支持绝对路径,只能是配合 --proto_path
指定来查找。
所以,回到上面这个例子:
protoc -I=proto --go_out=:. proto/hello.proto
我既指定了 --proto_path
为:proto
文件夹,又去读取包含这个目录的读取方式:proto/hello.proto
,结果没报错。所以,它应该是对比了一下,这2个目录结构是否相等 (two paths (e.g. absolute and relative) are equivalent )
如果相等,就直接读取了,不拼接路径:
protoc -I=proto --go_out=:. proto/hello.proto
# /proto/hello.proto
如果不相等,就去拼接这2个选项的路径来读取:
protoc -I=proto --go_out=:. b/c/hello.proto
# /proto/b/chello.proto
所以,如果不指定-I
参数,那么就不会拼接和做相等匹配,直接读取这个路径的文件。如果不存在就报错。
所以最佳实践是去掉-I 参数,直接读取当前目录下得proto文件
protoc --go_out=:. hello.proto
4. 执行多个proto文件
是可以同时执行多个proto文件的。多个文件用空格分开
protoc --go_out=:. hello.proto server.proto
这样就会在当前目录生成2个pb文件。但是会去校验变量去重,这个以后再讲。
也可以用统配符号 *
来匹配多个文件:
protoc --go_out=:. *.proto
5. 指定grpc选项,生成grpc功能
我们一般如果是go语言的话,用proto 都是结合grpc来用的。要结合--go_out
来使用:
protoc --go_out=plugins=grpc:. hello.proto
要想使用grpc功能,那么proto文件里得定义rpc相关的服务,这样生成的pb.go文件,才会生成相关rpc数据:
vi hello.proto
package proto;
option go_package = ".;proto";
message Yang {
string name=1;
int32 age=2;
}
message Id {
int32 uid=1;
}
# 要生成server rpc代码
service ServiceSearch{
rpc SaveUser(Yang) returns (Id){}
rpc UserInfo(Id) returns (Yang){}
}
再来看:.
的问题。上面没有生成gprc 插件,所以:.
= .
,那现在我们需要使用grpc插件,那么 这个 :
就是一个分隔符了,分隔成2部分:
--go_out === plugins的类型 : 输出的路径
protoc --go_out=plugins=grpc:./b/c hello.proto
他表示的是:用grpc服务生成的pb.go 文件,放入当前目录的 /b/c 目录下,如下图所示:
.
├── b
│ └── c
│ └── hello.pb.go
├── hello.proto
└── server.proto
执行命令的时候,会去检查你是否已经下载了 grpc 的库,google.golang.org/grpc
,没有的话,会去自动下载。
所以一般情况下,我们也不会搞这么复杂,所以最佳实践是生成的grpc服务的pb.gp文件也生成在当前的proto文件夹目录下
这样子写:
protoc --go_out=plugins=grpc:. hello.proto
不需要grpc:
protoc --go_out=. hello.proto
常用的执行命令就的学习完毕了,下一篇讲 proto文件的语法和规则