protobuf就是一种数据格式,如json,xml等

1.安装proto工具,并copy到path/bin下 https://github.com/protocolbuffers/protobuf/releases
2.go get -u github.com/golang/protobuf/protoc-gen-go

  1. protoc-gen-go在两个地方都有
  2. https://github.com/golang/protobuf/tree/master/protoc-gen-go
  3. https://github.com/protocolbuffers/protobuf-go/tree/master/cmd/protoc-gen-go

image.png

protoc 和 protoc-gen-go 产生的 proto 文件代码对比
$ protoc —go_out=./go/ ./proto/helloworld.proto
$ protoc —go_out=plugins=grpc:./go2/ ./proto/helloworld.proto
上面两个命令,第一个产生的只是 protobuffer 文件序列化和反序列化的代码。
第二个产生的则除了第一个的代码外,还增加服务器和客户端通讯、实现的公共库代码。

走通流程

1.编写proto文件, person.proto

  1. syntax = "proto3"; //首行声明使用的protobuf版本为proto3
  2. package serialize; // 声明所在包
  3. // package 项目名.模块名; // 常见的命名
  4. option go_package = "domain.com/projectName/serialize;proto"; // 生成的go文件包名
  5. // message person 会生成 Person 命名的结构体
  6. message person {
  7. int32 id = 1;
  8. string name = 2;
  9. }
  10. // message all_person 会按照驼峰规则自动生成名为AllPerson 的结构体
  11. message all_person {
  12. repeated person Per = 1;
  13. }
  1. //--------------枚举-----------------------
  2. 一般的枚举的第一个字段使用
  3. enum PhoneType {
  4. UNSPECIFIED = 0; // 未使用,第一个必须是0
  5. MOBILE = 1;
  6. }
  7. 编译为go文件就是
  8. type PhoneType int32
  9. const (
  10. PhoneType_UNSPECIFIED PhoneType = 0 // 未使用,第一个必须是0
  11. PhoneType_MOBILE PhoneType = 1 //第一个必须是0
  12. )
  13. func (x Order_State) Enum() *Order_State {
  14. p := new(Order_State)
  15. *p = x
  16. return p
  17. }
  18. func (x PhoneType) String() string {
  19. return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
  20. }
  21. func (PhoneType) Descriptor() protoreflect.EnumDescriptor {
  22. return file_order_proto_order_proto_enumTypes[4].Descriptor()
  23. }
  24. func (PhoneType) Type() protoreflect.EnumType {
  25. return &file_order_proto_order_proto_enumTypes[4]
  26. }
  27. func (x PhoneType) Number() protoreflect.EnumNumber {
  28. return protoreflect.EnumNumber(x)
  29. }
  30. // Deprecated: Use Order_State.Descriptor instead.
  31. func (PhoneType) EnumDescriptor() ([]byte, []int) {
  32. return file_order_proto_order_proto_rawDescGZIP(), []int{1, 1}
  33. }
  34. // 枚举类型还会生成两个字典
  35. var (
  36. PhoneType_name = map[int32]string{
  37. 0: "UNSPECIFIED",
  38. 1: "MOBILE",
  39. }
  40. PhoneType_value = map[string]int32{
  41. "UNSPECIFIED": 0,
  42. "MOBILE": 1,
  43. }
  44. )
  45. -----------------------------------------------
  46. message MyMessage1 {
  47. enum EnumAllowingAlias {
  48. option allow_alias = true; // 如果枚举有相同的值,设置allow_alias = true,否则报错
  49. UNKNOWN = 0;
  50. STARTED = 1;
  51. RUNNING = 1;
  52. }
  53. }
  54. type MyMessage1_EnumAllowingAlias int32
  55. const (
  56. MyMessage1_UNKNOWN MyMessage1_EnumAllowingAlias = 0
  57. MyMessage1_STARTED MyMessage1_EnumAllowingAlias = 1
  58. MyMessage1_RUNNING MyMessage1_EnumAllowingAlias = 1
  59. )
  60. //--------------嵌套结构体-----------------------
  61. 如果要引用内部类,则通过parent.type方式来调用
  62. message Outer { // Level 0
  63. message MiddleAA { // Level 1
  64. message Inner { // Level 2
  65. required int64 ival = 1;
  66. optional bool booly = 2;
  67. }
  68. }
  69. message MiddleBB { // Level 1
  70. message Inner { // Level 2
  71. required int32 ival = 1;
  72. }
  73. }
  74. }
  75. //--------------Maps-----------------------
  76. map<key_type, value_type> map_field = N;
  77. key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型
  78. map<string, Project> projects = 3;
  79. map 类型的字段不可重复(不能用 repeated 修饰)
  80. //--------------reserved---------------------
  81. message Foo {
  82. reserved 2, 15, 9 to 11;
  83. reserved "foo", "bar";
  84. }
  85. 如果通过完全删除某个字段或对其进行注释来更新消息类型,则将来的用户可以在对该类型进行自己的更新时重用该
  86. 字段编号。如果以后加载相同.proto的旧版本,这可能会导致严重问题,包括数据损坏、隐私漏洞等。确保不会发生
  87. 这种情况的一种方法是指定保留已删除字段的字段号(和/或名称,这也可能导致JSON序列化问题)。
  88. //--------------deprecated--------------------- 弃用
  89. message FooResponse {
  90. repeated Order orders = 1 [ deprecated = true ];
  91. int32 id = 2;
  92. }
  93. //--------------json_name---------------------
  94. message FooResponse {
  95. repeated Order orders = 1 [ json_name = "Order" ];
  96. int32 id = 2;
  97. }
  1. 进入proto 目录,使用 protoc 编译 person.proto
  • protoc —go_out=. person.proto
  • 执行完后会生成一个 person.pb.go 文件

3.使用

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/golang/protobuf/proto"
  5. "io/ioutil"
  6. "study/grpc/protobuf/serializa"
  7. )
  8. func main() {
  9. var p1 serialize.Person
  10. p1.Id=1
  11. p1.Name="tom"
  12. var p2 serialize.Person
  13. p2.Id=2
  14. p2.Name="jack"
  15. all_p := serialize.AllPerson{Per:[]*serialize.Person{&p1,&p2}}
  16. // 对数据进行序列化
  17. //注意参数一定要是指针类型,AllPerson实现了message接口,
  18. //在message内部就拿不到AllPerson的地址了
  19. data,err :=proto.Marshal(&all_p)
  20. if err !=nil{
  21. fmt.Println("error:",err.Error())
  22. return
  23. }
  24. ioutil.WriteFile("proto.dat",data,0777)
  25. data2,err :=ioutil.ReadFile("proto.dat")
  26. if err !=nil{
  27. fmt.Println("error:",err.Error())
  28. return
  29. }
  30. // 对数据进行反序列化
  31. var tag serialize.AllPerson
  32. proto.Unmarshal(data2,&tag)
  33. for _,v:=range tag.Per{
  34. fmt.Println(v)
  35. }
  36. }
  37. //---------------------------------
  38. id:1 name:"tom"
  39. id:2 name:"jack"

生成的proto.dat
image.png

数据类型

image.png

语法

  1. syntax = "proto3";
  2. message SearchRequest {
  3. string query = 1;
  4. int32 page_number = 2;
  5. int32 result_per_page = 3;
  6. }
  • 语法说明(syntax)前只能是空行或者注释
  • 每个字段由【字段限制】、字段类型、字段名和编号四部分组成

字段限制

  • required:必须赋值的字段,proto3已不支持
  • optional:可有可无的字段,改名为 “singular”,默认就是这个,不能显示的添加该限制
  • repeated:可重复字段

编号

  • 身份编号,1-2^{29}-1,但不能使用19000到19999之间的值

导包

import搜索路径, 在使用protoc编译时,需要使用选项-I或—proto_path通知protoc去什么地方查找import的文件,如果不指定,protoc将会在当前目录(即调用protoc的路径)下查找。

  1. import "xx/xx/xx.proto":导入其他的包,这样你就可以直接使用其他的包的数据结构
  2. 例如:xx/xx/a.proto 定义了message AAA
  3. xx/xx/b.proto 中可直接使用a.AAA
  4. 如果b.proto a.protopackage一致,在b.proto 直接使用AAA(而不是a.AAA)

注意:例如现在有3个文件:a,b,c,b导入了a,c导入了b,c是不能拿到a的数据结构,解决:b再导入a时,使用 import public “a.proto” 就可以了

protoc参数

Usage: protoc [OPTION] PROTO_FILES

  • -I 如果多个proto文件之间有互相依赖,生成某个proto文件时,需要import其他几个proto文件,这时候就要用-I来指定搜索目录,如果没有指定,则在当前目录将座位搜索目录
  • —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.
  • —javanano_out=OUT_DIR Generate Java Nano source file.
  • —js_out=OUT_DIR Generate JavaScript source.
  • —objc_out=OUT_DIR Generate Objective C header and source.
  • —python_out=OUT_DIR Generate Python source file.
  • —ruby_out=OUT_DIR Generate Ruby source file.

举例

项目结构:
AAAAA
common
common.proto
common.pb.go
pubplan
pubplan.proto

cd 到AAAAA的上一级目录 执行:

  • protoc AAAAA/pubplan/pubplan.proto —go_out=plugins=grpc,paths=source_relative:.
  • 会在AAAAA/pubplan下生成go文件,并且

common.proto

  1. syntax = "proto3";
  2. package common;
  3. //ssh直连信息
  4. message SshInfoStruct {
  5. string User = 1;
  6. string PassWord = 2;
  7. string Host = 3;
  8. int64 Port = 4;
  9. string KeyPath = 5;
  10. }
  11. // 通用请求体
  12. message Request {
  13. string cmd = 1;
  14. SshInfoStruct info = 2;
  15. }

pubplan.proto

  1. syntax = "proto3";
  2. package pubplan;
  3. import "AAAAA/common/common.proto";
  4. message PlanAppPubReq{
  5. common.SshInfoStruct info = 1;
  6. string appName = 2;
  7. string middlewareName = 3;
  8. int64 pubMode = 4;
  9. string extParam = 5;
  10. string targetPath = 6;
  11. string serverNode = 7;
  12. string ecsTempPath = 8;
  13. string ecsBakPath = 9;
  14. string fileName = 10;
  15. string appProcessName = 11;
  16. bool isRepair = 12;
  17. }

其它

Any

Any消息类型允许您将消息作为嵌入类型使用,而无需使用它们的.proto定义。Any包含一个以字节表示的任意序列化消息,以及一个URL,该URL充当该消息的全局唯一标识符并解析为该消息的类型。

  1. import "google/protobuf/any.proto";
  2. message Response {
  3. uint32 Code = 1;
  4. string Msg = 2;
  5. google.protobuf.Any data = 3;
  6. }

生成的结构体

  1. type Response struct {
  2. state protoimpl.MessageState
  3. sizeCache protoimpl.SizeCache
  4. unknownFields protoimpl.UnknownFields
  5. Code uint32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
  6. Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"`
  7. Data *any.Any `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
  8. }

测试

  1. package main
  2. import (
  3. "fmt"
  4. "google.golang.org/protobuf/proto"
  5. "google.golang.org/protobuf/types/known/anypb"
  6. myprto "commons/grpc2/proto"
  7. )
  8. func main() {
  9. marshal := &myprto.TestAny{
  10. Id: 1,
  11. Title: "标题",
  12. Content: "内容",
  13. }
  14. any, err := anypb.New(marshal)
  15. fmt.Println(any, err) // [type.googleapis.com/rpc.TestAny]:{Id:1 Title:"标题" Content:"内容"} <nil>
  16. msg := &myprto.Response{
  17. Code: 0,
  18. Msg: "success",
  19. Data: any,
  20. }
  21. fmt.Println(msg) // Msg:"success" data:{[type.googleapis.com/rpc.TestAny]:{Id:1 Title:"标题" Content:"内容"}}
  22. unmarshal := &myprto.TestAny{}
  23. err = anypb.UnmarshalTo(msg.Data, unmarshal, proto.UnmarshalOptions{})
  24. fmt.Println(unmarshal, err) // Id:1 Title:"标题" Content:"内容" <nil>
  25. }

调整默认tag

生成的结构体

  1. type MyMessage struct {
  2. state protoimpl.MessageState
  3. sizeCache protoimpl.SizeCache
  4. unknownFields protoimpl.UnknownFields
  5. // @inject_tag: json:"Code"
  6. Code int64 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code"`
  7. }

extend

proto3 不支持extension,只支持option, option传值设置:
https://developers.google.com/protocol-buffers/docs/proto3#options
具体option可以分为:

  • FileOptions
  • MessageOptions
  • FieldOptions
  • OneofOptions
  • EnumOptions
  • EnumValueOptions
  • ServiceOptions
  • MethodOptions
  • UninterpretedOption

详见:https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto

作用:见字意为一个扩展,详细的可以理解是为结构体添加一个或多个字段,但是不能显示的获取或设置值。下面为MessageOptions的一个例子
test.proto

  1. syntax = "proto3";
  2. package protobuf;
  3. import "google/protobuf/descriptor.proto";
  4. option go_package = "protobuf;protobuf";
  5. // extend 编号范围为50000-99999
  6. extend google.protobuf.MessageOptions {
  7. string AAA = 50000 ;
  8. uint64 BBB = 50001 ;
  9. }

test1.proto

  1. syntax = "proto3";
  2. package protobuf;
  3. import "grpc/protobuf/test.proto";
  4. message MyPerson {
  5. option (AAA) = "aaa";
  6. string BBB = 1;
  7. }

拿到option字段:

  1. package main
  2. import (
  3. pb "commons/grpc/protobuf"
  4. "google.golang.org/protobuf/descriptor"
  5. "google.golang.org/protobuf/proto"
  6. "log"
  7. )
  8. func main() {
  9. p := pb.MyPerson{}
  10. _, pd := descriptor.MessageDescriptorProto(&p)
  11. if v, err := proto.GetExtension(pd.Options, pb.E_AAA); err == nil {
  12. log.Printf("GetExtensions(pf.Options, pb.E_AAA) file_opt1:%v", *(v.(*string)))
  13. } else {
  14. log.Fatalf("GetExtensions(pf.Options, pb.E_AAA) failed: %v", err)
  15. }
  16. newv := "aaa_reset"
  17. if err := proto.SetExtension(pd.Options, pb.E_AAA, &newv); err != nil {
  18. log.Fatalf("SetExtension(pf.Options, pb.E_AAA) failed: %v", err)
  19. }
  20. if v, err := proto.GetExtension(pd.Options, pb.E_AAA); err == nil {
  21. log.Printf("GetExtensions(pf.Options, pb.E_AAA) file_opt1:%v", *(v.(*string)))
  22. } else {
  23. log.Fatalf("GetExtensions(pf.Options, pb.E_AAA) failed: %v", err)
  24. }
  25. }
  26. 2021/02/28 22:38:30 GetExtensions(pf.Options, pb.E_AAA) file_opt1:aaa
  27. 2021/02/28 22:38:30 GetExtensions(pf.Options, pb.E_AAA) file_opt1:aaa_reset

JSON 映射

Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。

如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。

proto3 JSON JSON示例 注意
message object {“fBar”: v, “g”: null, …} 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值
enum string “FOO_BAR” 枚举值的名字在proto文件中被指定
map object {“k”: v, …} 所有的键都被转换成string
repeated V array [v, …] null被视为空列表
bool true, false true, false
string string “Hello World!”
bytes base64 string “YWJjMTIzIT8kKiYoKSctPUB+”
int32, fixed32, uint32 number 1, -10, 0 JSON值会是一个十进制数,数值型或者string类型都会接受
int64, fixed64, uint64 string “1”, “-10” JSON值会是一个十进制数,数值型或者string类型都会接受
float, double number 1.1, -10.0, 0, “NaN”, “Infinity” JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受
Any object {“@type”: “url”, “f”: v, … } 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{“@type”: xxx, “value”: yyy}否则,该值会被转换成一个JSON对象,@type字段会被插入所指定的确定的值
Timestamp string “1972-01-01T10:00:20.021Z” 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数
Duration string “1.000340012s”, “1s” 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度
Struct object { … } 任意的JSON对象,见struct.proto
Wrapper types various types 2, “2”, “foo”, true, “true”, null, 0, … 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null
FieldMask string “f.fooBar,h” 见fieldmask.proto
ListValue array [foo, bar, …]
Value value 任意JSON值
NullValue null JSON null

any的例子

  1. package google.profile;
  2. message Person {
  3. string first_name = 1;
  4. string last_name = 2;
  5. }
  6. {
  7. "@type": "type.googleapis.com/google.profile.Person",
  8. "firstName": <string>,
  9. "lastName": <string>
  10. }

引入 google 定义的 pb 出错

先将protobuf 下载后放 gopath src 下
image.png
再配置 goland
image.png