文档章节

gogoprotobuf使用(上)

alexstocks
 alexstocks
发布于 2015/03/14 16:44
字数 1625
阅读 6978
收藏 73

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

  声明:版权所有,谢绝转载。

   在go中使用google protobuf,有两个可选用的包: goprotobuf(go官方出品)和gogoprotobuf(gogo组织出品^_^)。

    gogoprotobuf能够完全兼容google protobuf。而且经过我一些测试,它生成大代码质量确实要比goprotobuf高一些,主要是它在goprotobuf之上extend了一些option。这些option也是有级别区分的,有的option只能修饰field,有的可以修饰enum,有的可以修饰message,有的是修饰package(即对整个文件都有效)。下面一一说明其一些选项的意义。

    另外,本文的所有proto示例都是proto v3格式。

1 gogoproto.goproto_enum_prefix 

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

pb code:
enum E {
    // option (gogoproto.goproto_enum_prefix) = false;
    A = 0;
    B = 2;
}
go code:
const (
	// option (gogoproto.goproto_enum_prefix) = false;
	E_A E = 0
	E_B E = 2
)

or
pb code:
enum E {
    // option (gogoproto.goproto_enum_prefix) = false;
    A = 0;
    B = 2;
}
go code:
const (
	A E = 0
	B E = 2
)

2 gogoproto.goproto_getters

如果选项为false,则不会为message的每个field生成一个Get函数。

pb code:
message test {
    // option (gogoproto.goproto_getters) = false;
    E e = 1;
}
go code:
type Test struct {
	E                *E     `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Test) GetE() E {
	if m != nil && m.E != nil {
		return *m.E
	}
	return A
}

or

pb code:
message test {
    option (gogoproto.goproto_getters) = false;
    E e = 1;
}

go code:
type Test struct {
	E                *E     `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"`
	XXX_unrecognized []byte `json:"-"`
}

3 gogoproto.face

 当这个选项为true的时候,会为message生成相应的interface。

message test {
	option (gogoproto.goproto_getters) = false;
	option (gogoproto.face) = true;
	string msg = 1;
}

type Test struct {
	Msg              *string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
	XXX_unrecognized []byte  `json:"-"`
}

func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}

func init() {
	proto.RegisterEnum("test.E", E_name, E_value)
}

type TestFace interface {
	Proto() github_com_gogo_protobuf_proto.Message
	GetMsg() *string
}

func (this *Test) Proto() github_com_gogo_protobuf_proto.Message {
	return this
}

func (this *Test) TestProto() github_com_gogo_protobuf_proto.Message {
	return NewTestFromFace(this)
}

func (this *Test) GetMsg() *string {
	return this.Msg
}

func NewTestFromFace(that TestFace) *Test {
	this := &Test{}
	this.Msg = that.GetMsg()
	return this
}

这个选项要求goproto_getters选项为false,即只生成interface相应的method。否则,你会收到如下error:

panic: face requires getters to be disabled please use gogoproto.getters or gogoproto.getters_all and set it to false

goroutine 1 [running]:
github.com/gogo/protobuf/plugin/face.(*plugin).Generate(0x2086c00a0, 0x20870b780)
	/Users/Alex/bin/go/src/github.com/gogo/protobuf/plugin/face/face.go:164 +0x37d
github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).runPlugins(0x2087001c0, 0x20870b780)
	/Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1008 +0x91
github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).generate(0x2087001c0, 0x20870b780)
	/Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1047 +0x3e1
github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).GenerateAllFiles(0x2087001c0)
	/Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:994 +0x249
main.main()
	/Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/main.go:96 +0x31d
--gogo_out: protoc-gen-gogo: Plugin failed with status code 2.

4 gogoproto.nullable

有没有注意到上面的示例中Test的成员msg类型为string*,当你要向它赋值的时候,可能要写出如下代码:

var test Test
test.Msg = new(string)
*test.Msg = "test.msg"

or

test := Test{Msg:proto.String("hello")}

是不是感觉很麻烦?而且生成一堆临时对象,不利于gc。此时如果nullable选项为false,就不用这么麻烦了

pb code:
message test {
	string msg = 1 [(gogoproto.nullable) = false];
}
go code:
type Test struct {
	Msg              string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

严格地说,nullable这个option是违背protobuf的初衷的。使用了它之后,message序列化的时候,gogo会为message的每个field设置一个值,而google protobuf则是要求如果一个option的field没有被赋值,则序列化的时候不会把这个成员序列化进最终结果的。gogo官方的详尽解释是:

    The protocol buffer specification states, somewhere, that you should be able to tell whether a field is set or unset. With the option nullable=false this feature is lost, since your non-nullable fields will always be set. It can be seen as a layer on top of protocol buffers, where before and after marshalling all non-nullable fields are set and they cannot be unset.

            ref: https://godoc.org/code.google.com/p/gogoprotobuf/gogoproto

注意: bytes成员不要使用这个option,否则会收到编译警告“WARNING: field Message.Data is a non-nullable bytes type, nullable=false has no effect”

5 gogoproto.customname

在生成的代码中修改成员的名称,这个选项在这种情况下非常有用:field的名称与message的method的名称一样。

pb code:
message test {
	option (gogoproto.goproto_getters) = false;
	string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
}
go code:
type Test struct {
	MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

类似的选项还有gogoproto.customtype,不再赘述。

6 gogoproto.goproto_stringer

此选项不设置的时候,其为true。当这个选项为false的时候,gogo不再为message对一个的struct生成String()方法。这个选项建议不要设置为false。

pb code:
message test {
	option (gogoproto.goproto_stringer) = true;
	string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
}
go code:
type Test struct {
	MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}

or
pb code:
option (gogoproto.goproto_getters_all) = false;

message test {
	option (gogoproto.goproto_stringer) = false;
	string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
}
go code:
type Test struct {
	MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Test) Reset()      { *m = Test{} }
func (*Test) ProtoMessage() {}

7 gogoproto.gostring

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

pb code:
option (gogoproto.goproto_getters_all) = false;

message test {
	option (gogoproto.gostring) = true;
	string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
}
go code:
type Test struct {
	MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}

func init() {
	proto.RegisterEnum("test.E", E_name, E_value)
}
func (this *Test) GoString() string {
	if this == nil {
		return "nil"
	}
	s := strings.Join([]string{`&test.Test{` +
		`MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
		`XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
	return s
}

gogoproto.stringer_all

pb code:
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.gostring_all) = true;

message test {
	string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
}
go code:
type Test struct {
	MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}

func init() {
	proto.RegisterEnum("test.E", E_name, E_value)
}
func (this *Test) GoString() string {
	if this == nil {
		return "nil"
	}
	s := strings.Join([]string{`&test.Test{` +
		`MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
		`XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
	return s
}

测试用例:

package main

import (
    "fmt"
    "strings"
)

type Test struct {
    MyMsg            string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    XXX_unrecognized []byte `json:"-"`
}

func (this *Test) GoString() string {
    if this == nil {
        return "nil"
    }
    s := strings.Join([]string{`&test.Test{` +
        `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
        `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
    return s
}

func main() {
    var test Test
    test.MyMsg = "hello, world!"
    fmt.Printf("%#v\n", test)
}

   8 populate & populate_all

     这个选项为message级别,当设置其值为true的时候,gogo会为每个message生成一个NewPopulated函数。

pb code:
 option (gogoproto.populate_all) = true;

  message B {
	optional A A = 1 [(gogoproto.nullable) = false, (gogoproto.embed) = true];
	repeated bytes G = 2 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uint128", (gogoproto.nullable) = false];
  }
  
go code:

 func NewPopulatedB(r randyExample, easy bool) *B {
	this := &B{}
	v2 := NewPopulatedA(r, easy)
	this.A = *v2
	if r.Intn(10) != 0 {
		v3 := r.Intn(10)
		this.G = make([]github_com_gogo_protobuf_test_custom.Uint128, v3)
		for i := 0; i < v3; i++ {
			v4 := github_com_gogo_protobuf_test_custom.NewPopulatedUint128(r)
			this.G[i] = *v4
		}
	}
	if !easy && r.Intn(10) != 0 {
		this.XXX_unrecognized = randUnrecognizedExample(r, 3)
	}
	return this
  }

 如果gogo为message生成了test函数,这些函数就会调用NewPopulated函数。如果用户没有使用这个选项但是使用了test选项,则用户需自定义NewPopulated函数。

由于oschina对博文长度有限制,剩余部分转下文《gogoprotobuf使用(下)》

 

© 著作权归作者所有

下一篇: redis的一些坑
alexstocks

alexstocks

粉丝 21
博文 36
码字总数 38417
作品 2
海淀
私信 提问
加载中

评论(5)

ddatsh
ddatsh

引用来自“小鸽子咕噜”的评论

fuck goods
+1
alexstocks
alexstocks 博主

引用来自“fordoo”的评论

https://github.com/alecthomas/go_serialization_benchmarks 这个测试里面 Gogoprotobuf速度最快
toil
toil
https://github.com/alecthomas/go_serialization_benchmarks 这个测试里面 Gogoprotobuf速度最快
小鸽子咕噜
小鸽子咕噜
fuck goods
zouqilin
zouqilin
干货
gogoprotobuf使用(下)

声明:版权所有,谢绝转载。 承接上文《gogoprotobuf使用(上)》,继续说明gogoprotobuf的各个option。 9 gogoproto.testgen & gogoproto.testgen_all testgen选项为true,则gogo会为相应的m...

alexstocks
2015/03/14
3K
0
Go 更灵活地编解码 thrift 消息

在 Go 语言里使用 thrift 进行编解码的过程中,我们发现了一些不够灵活的地方。所以我们写了一个新的 thrift 编解码实现,完全兼容 thrift 协议,更灵活而且性能更高。thrifter 项目地址:t...

陶文
2018/01/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

没有更多内容

在每个GROUP BY组中选择第一行?

顾名思义,我想选择以GROUP BY分组的每组行的第一行。 具体来说,如果我有一个如下的purchases表: SELECT * FROM purchases; 我的输出: id | customer | total---+----------+------ 1...

技术盛宴
35分钟前
5
0
python 安装与使用总结

https://www.jetbrains.com/pycharm/download/#section=mac

T型人才追梦者
38分钟前
5
0
每个开发人员都应该知道的11个Linux命令

本文主要挑选出读者有必要首先学习的 11 个 Linux 命令,如果不熟悉的读者可以在虚拟机或云服务器上实操下,对于开发人员来说,能熟练掌握 Linux 做一些基本的操作是必要的! 事不宜迟,这里...

武培轩
47分钟前
7
0
window.onload与$(document).ready()

JavaScript的window.onload和jQuery的$(document).ready()方法有什么区别? #1楼 关于在Internet Explorer中使用$(document).ready()的警告。 如果在整个文档加载之前 HTTP请求被中断(例如,...

javail
50分钟前
8
0
对比yml配置文件与properties的区别

我们在日常编码中少不了配置文件,说到配置文件就不得不说起yml和properties这两种后缀的配置文件 接下来我带大家简述一下他们具体有什么区别 - yml格式的文件 server: port: 9090 spring: a...

理性思考
53分钟前
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部