上一篇gRPC-shop:什么是 gRPC(一)主要介绍了 gRPC 基础概念,gRPC 采用 Protobuf 作为它的序列化协议格式,那么这篇文章我们简单介绍一下 Protobuf。
Protobuf 是什么
Protobuf全称是Protocal buffers ,平时我们都会简称为pb,下文就使用pb代称了。
官网中对pb的完整定义如下:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
简单的说, pb是一种与语言、平台无关,可扩展的序列化数据格式。它的定位类似于JSON、XML,但是比他们更小、更快、更简单。
对于开发者来说,我们只需要定义proto文件。然后使用对应的IDL编译器把.proto文件编译成对应语言的代码即可。
从某个角度来看,.proto文件就是接口。里面包含了接口方法、入参、出参。
如何使用
环境安装可以看这篇[1]。
来看一个简单的示例,我们创建一个order.proto文件,内容如下:
逐行解释。
pb有两个版本,默认版本是proto2,如果要使用proto3,我们就得在非空非注释的第一行代码中使用syntax = "proto3";声明版本。
option表示可选项配置。有些选项是文件级别的,需要在一个文件的顶级作用域定义,比如上面的option go_package = "order";。
go_package选项,定义的值,就是把pb文件编译生成Go代码后文件的package名。这样其他生成的Go代码就可以通过关键字import使用到这个包。
你可能还会在.proto文件中看到类似package xxx这样的, 比如,
package主要用来解决不同的pb文件存在相同消息类型名称之间的冲突。比如a.proto有一个Common message,b.proto也有一个Common message,此时需要通过package来做区分。
那package和go_package有什么关系吗?
There is no correlation between the Go import path and the
packagespecifier in the.protofile. The latter is only relevant to the protobuf namespace, while the former is only relevant to the Go namespace. Also, there is no correlation between the Go import path and the.protoimport path.
没有关系。
package只和pb空间有关,而go_package前面解释过了,和Go的import包有关,他们两者,本质上不是一个层面的。
接着我们定义了一个Order的消息message类型,定义消息类型是使用关键字message定义的。
Order定义了四个字段,对应string、uint32以及bool字段类型。
消息类型中字段定义的格式为,
默认情况下,每个字段最前面的修饰符为singular,一般省略不写。
假设一个主订单下有多个子订单,我们可以如何定义?
我们新定义了一个OrderItem子订单的消息类型。然后在Order中,我们使用了OrderItem作为items字段的类型值。
repeated是另一种修饰符,表示允许字段重复。上面的场景即一个订单有多个子订单的概念。
编译成Go代码,items会变成slice类型。
其他类型就不一一介绍,更多的类型可以查看这:Protocol Buffers v3.0.0[2]
接着我们通过命令编译(确保工具已装)生成Go相关的代码,执行如下命令:
--proto_path指定要编译的源码的搜索路径,上面的.表示当前目录。如果不指定--proto_path的话,默认为pwd。所以上面我们也可以省略成这样,
--go_out参数告知protoc编译器去加载对应的protoc-gen-go工具且指定编译生成Go代码后保存位置。当然如果是生成php代码那就是--php_out。对应的语言,对应--xx_out。
最后的*.proto表示搜索当前目录下所有.proto文件。
那么整句命令行意思就是:编译当前目录下所有.proto文件, 把生成的Go代码存放到当前目录。
上面的命令执行后,当前目录下多了一个order.pb.go文件。生成的数据结构:
可以看到生成的主订单结构中,Items是一个切片类型。同时生成的还有一些Getxxx方法,方便读取字段值。
等等,接口呢?
message有了,现在我们可以在.proto文件定义RPC服务接口了。
我们定义了一个 OrderService的服务,提供了一个GetOrderList的接口,它的入参是OrderListReq,返回类型是OrderListResp,是不是很像我们平常定义的函数。
和平常自定义函数的不同在于,可以自定义无入参出参的函数,而在pb中接口必须携带参数,否则编译的时候会报,
Expected type name
针对不需要参数的情况,我们一般会定义一个空message类型或者传入google.protobuf.Empty,传入google.protobuf.Empty不好点在于不易扩展,万一这个接口要参数了呢。
service定义完毕,再次执行上述命令,你会发现,重新生成的order.pb.go文件并没有变化。这是因为RPC框架很多,protoc编译器并不知道给我们生成哪个RPC框架的代码。
protoc-gen-go内部已经集成了一个名为gRPC的插件,所以我们可以通过命令行参数--go-grpc_out生成gRPC代码:
然后你就发现这个错误,
我们需要改动option go_package = ".;order"为option go_package = "./;order"。
再次执行,多出了一个order_grpc.pb.go文件。里面就能看到grpc客户端和服务端的api服务。
总结
这篇主要介绍了pb一些基础的知识,包括定义message、service以及通过protoc-gen-go生成对应的Go代码,gRPC代码。关于 protoc命令,后面有必要再单独写一篇文章。
下一篇,通过生成的Go代码,完成gRPC通信,然后正式步入主题。
参考
- https:/github.com/protocolbuffers/protobuf/releases/tag/v3.0.0[1]
- https://go.aabbccm.com/docs/main/env[2]
- https://grpc.io/docs/languages/go/quickstart/
- https://developers.google.com/protocol-buffers
- https://colobu.com/2019/10/03/protobuf-ultimate-tutorial-in-go/#option
- https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-02-pb-intro.html










