zl程序教程

您现在的位置是:首页 >  其他

当前栏目

golang源码分析:gogoproto

2023-02-18 16:32:25 时间

针对golang 使用proto,有两个功能增强可选包goprotobuf(go官方出品)和gogoprotobuf地址如下

go get -u code.google.com/p/goprotobuf
//或者 github.com/go-mirrors/goprotobuf
go get -u github.com/gogo/protobuf

gogoprotobuf是完全兼容google protobuf,它生成的代码质量要比goprotobuf高一些。它的扩展功能可以参考文档

https://github.com/gogo/protobuf/blob/master/extensions.md

整体上可以分为6类增强:

1,加快序列化和反序列化速度:Fast Marshalling and Unmarshalling

比如

gogoproto.marshaler 
gogoproto.sizer
gogoproto.marshaler_all 
gogoproto.sizer_all

sizer选项true,gogo会相应的message生成

func Size() int

marshaler为true,gogo为相应的生成:

func Marshal()([] byte, int)

这个 method会调用Size(),所以marshaler为true的时候,sizer也必须为true。

2,更多可选的go结构:More Canonical Go Structures

gogoproto.nullable

nullable这个option违背protobuf的初衷。使用它,message序列化后,gogo为message的每个field设置一个值,而google protobuf则是要求如果一个option的field没有被赋值,则序列化的时候不会把这个成员序列化进最终结果的。

gogoproto.customname

field的名称与message的method的名称一样。

还有

gogoproto.customtype

自定义类型

3,对于一些不兼容protobuf的功能做成可选项:Goprotobuf Compatibility

gogoproto.goproto_enum_prefix

如果选项为false,则生成的代码中不加"E_"。

4,生成一些常见的方法,减少我们的代码编写量:Less Typing

gogoproto.gostring

这个选项为message级别,为true的时候,gogo会为相应的message生成GoString()方法。如果想为所有的message生成这类函数,可以设置package级别的gogoproto.stringer_all为true。

5,生成测试代码和benchmark 代码

gogoproto.testgen 
gogoproto.testgen_all
gogoproto.benchgen

testgen选项为true,则gogo会为相应的message生成一个测试用例与性能测试用例。testgen_all则为相应的package level的option。

6,更多序列化格式:More Serialization Formats

gogoproto.jsontag
gogoproto.moretag

上面这些字段上的选项很多都可以是文件维度上的选项,具体可以参考文档。

下面我们定义一个proto文件来实战分析下

syntax = "proto3";
package test;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";

option go_package = "learn/grpc/gogoproto";
//定义服务
service TestService {
    rpc SayHello(Request) returns (Response){
    }
}
//定义参数类型
message Request {
     string message=1;
     string Field2 = 2 [(gogoproto.jsontag) = "MyField2", (gogoproto.moretags) = "xml:\",comment\""];
      oneof filed {
            int64 uid = 3 [(gogoproto.moretags) = 'form:"uid" validate:"required"'];
      }
     int32 bar = 4 [(gogoproto.moretags) = 'form:"more_bar"', (gogoproto.jsontag) = 'custom_tag'];
     string msg = 5 [(gogoproto.nullable) = true, (gogoproto.customname) = "MyMsg"];
     repeated bytes G = 6 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uint128", (gogoproto.nullable) = false];
}
message Response {
    string message=1;
}

可以看到和普通的proto文件区别是我们引入

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

这里面声明了扩展的定义,在每一个字段后面定义了

[(gogoproto.nullable) = true, (gogoproto.customname) = "MyMsg"];

格式的扩展,基本格式为[(key)=val,...]

接着我们生成下代码,首先只生成pb文件和grpc文件

protoc  --go-grpc_out=./hello  --proto_path=../../.. --proto_path=. --go_out=./hello --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative hello.proto

对比发现,生成的文件和扩展前的文件一样,说明是兼容proto的。

type Request struct {
  state         protoimpl.MessageState
  sizeCache     protoimpl.SizeCache
  unknownFields protoimpl.UnknownFields

  Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
  Field2  string `protobuf:"bytes,2,opt,name=Field2,proto3" json:"Field2,omitempty"`
  // Types that are assignable to Filed:
  //  *Request_Uid
  Filed isRequest_Filed `protobuf_oneof:"filed"`
  Bar   int32           `protobuf:"varint,4,opt,name=bar,proto3" json:"bar,omitempty"`
  Msg   string          `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"`
  G     [][]byte        `protobuf:"bytes,6,rep,name=G,proto3" json:"G,omitempty"`
}

要生gogo成扩展,需要安装扩展插件

go get -u  github.com/gogo/protobuf/protoc-gen-gogo

然后生成下代码

protoc  --go-grpc_out=./hello  --proto_path=../../.. --proto_path=. --go_out=./hello --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative --gogo_out=./hello  hello.proto
type Request struct {
  Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
  Field2  string `protobuf:"bytes,2,opt,name=Field2,proto3" json:"MyField2" xml:",comment"`
  // Types that are valid to be assigned to Filed:
  //  *Request_Uid
  Filed                isRequest_Filed                                `protobuf_oneof:"filed"`
  Bar                  int32                                          `protobuf:"varint,4,opt,name=bar,proto3" json:"custom_tag" form:"more_bar"`
  MyMsg                string                                         `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"`
  G                    []github_com_gogo_protobuf_test_custom.Uint128 `protobuf:"bytes,6,rep,name=G,proto3,customtype=github.com/gogo/protobuf/test/custom.Uint128" json:"G"`
  XXX_NoUnkeyedLiteral struct{}                                       `json:"-"`
  XXX_unrecognized     []byte                                         `json:"-"`
  XXX_sizecache        int32                                          `json:"-"`
}

对比下,我们发现生成的结构体发生了很多变化

 Field2  string `protobuf:"bytes,2,opt,name=Field2,proto3" json:"MyField2" xml:",comment"`
 Bar                  int32                                          `protobuf:"varint,4,opt,name=bar,proto3" json:"custom_tag" form:"more_bar"`

field2多了xml格式的tag,bar多了form格式的tag,并且form的名字改成了more_bar.

MyMsg                string                                         `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"`

生成的golang结构体的字段名字由以前的Msg变成了我们指定的Msg

 G                    []github_com_gogo_protobuf_test_custom.Uint128 `protobuf:"bytes,6,rep,name=G,proto3,customtype=github.com/gogo/protobuf/test/custom.Uint128" json:"G"`

G的类型由[][]byte变成了我们定义的类型

[]github_com_gogo_protobuf_test_custom.Uint128

可以进去看下

github.com/gogo/protobuf@v1.3.2/test/custom/custom.go

type Uint128 [2]uint64

有没有发现,如果我们的proto定义得足够好,可以少写好多代码。