gogoprotobuf使用(下)

聲明:版權全部,謝絕轉載。git

   

承接上文《gogoprotobuf使用(上)》,繼續說明gogoprotobuf的各個option。github

9 gogoproto.testgen & gogoproto.testgen_alljson

 testgen選項爲true,則gogo會爲相應的message生成一個測試用例與性能測試用例。testgen_all則爲相應的package level的option。app

pb code:
option (gogoproto.testgen_all) = true;
option (gogoproto.benchgen_all) = true;

message A {
	string msg = 1;
}

go code:
package test

import testing "testing"
import math_rand "math/rand"
import time "time"
import github_com_gogo_protobuf_proto "github.com/gogo/protobuf/proto"
import encoding_json "encoding/json"

func TestAProto(t *testing.T) {
	popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
	p := NewPopulatedA(popr, false)
	data, err := github_com_gogo_protobuf_proto.Marshal(p)
	if err != nil {
		panic(err)
	}
	msg := &A{}
	if err := github_com_gogo_protobuf_proto.Unmarshal(data, msg); err != nil {
		panic(err)
	}
	for i := range data {
		data[i] = byte(popr.Intn(256))
	}
	if !p.Equal(msg) {
		t.Fatalf("%#v !Proto %#v", msg, p)
	}
}

func BenchmarkAProtoMarshal(b *testing.B) {
	popr := math_rand.New(math_rand.NewSource(616))
	total := 0
	pops := make([]*A, 10000)
	for i := 0; i < 10000; i++ {
		pops[i] = NewPopulatedA(popr, false)
	}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		data, err := github_com_gogo_protobuf_proto.Marshal(pops[i%10000])
		if err != nil {
			panic(err)
		}
		total += len(data)
	}
	b.SetBytes(int64(total / b.N))
}

func BenchmarkAProtoUnmarshal(b *testing.B) {
	popr := math_rand.New(math_rand.NewSource(616))
	total := 0
	datas := make([][]byte, 10000)
	for i := 0; i < 10000; i++ {
		data, err := github_com_gogo_protobuf_proto.Marshal(NewPopulatedA(popr, false))
		if err != nil {
			panic(err)
		}
		datas[i] = data
	}
	msg := &A{}
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		total += len(datas[i%10000])
		if err := github_com_gogo_protobuf_proto.Unmarshal(datas[i%10000], msg); err != nil {
			panic(err)
		}
	}
	b.SetBytes(int64(total / b.N))
}

func TestAJSON(t *testing.T) {
	popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
	p := NewPopulatedA(popr, true)
	jsondata, err := encoding_json.Marshal(p)
	if err != nil {
		panic(err)
	}
	msg := &A{}
	err = encoding_json.Unmarshal(jsondata, msg)
	if err != nil {
		panic(err)
	}
	if !p.Equal(msg) {
		t.Fatalf("%#v !Json Equal %#v", msg, p)
	}
}
func TestAProtoText(t *testing.T) {
	popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
	p := NewPopulatedA(popr, true)
	data := github_com_gogo_protobuf_proto.MarshalTextString(p)
	msg := &A{}
	if err := github_com_gogo_protobuf_proto.UnmarshalText(data, msg); err != nil {
		panic(err)
	}
	if !p.Equal(msg) {
		t.Fatalf("%#v !Proto %#v", msg, p)
	}
}

func TestAProtoCompactText(t *testing.T) {
	popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano()))
	p := NewPopulatedA(popr, true)
	data := github_com_gogo_protobuf_proto.CompactTextString(p)
	msg := &A{}
	if err := github_com_gogo_protobuf_proto.UnmarshalText(data, msg); err != nil {
		panic(err)
	}
	if !p.Equal(msg) {
		t.Fatalf("%#v !Proto %#v", msg, p)
	}
}

10 gogoproto.marshaler & gogoproto.sizer  gogoproto.marshaler_all & gogoproto.sizer_all函數

   sizer選項爲true的時候,gogo會爲相應的message生成method:"func Size() int";當marshaler爲true的時候,gogo會爲相應的method生成method:func Marshal()([] byte, int),這個method會調用Size(),因此marshaler爲true的時候,sizer也必須爲true。post

pb code:
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;

message A {
	string msg = 1;
}

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

func (m *A) Size() (n int) {
	var l int
	_ = l
	if m.Msg != nil {
		l = len(*m.Msg)
		n += 1 + l + sovTest(uint64(l))
	}
	if m.XXX_unrecognized != nil {
		n += len(m.XXX_unrecognized)
	}
	return n
}

func sovTest(x uint64) (n int) {
	for {
		n++
		x >>= 7
		if x == 0 {
			break
		}
	}
	return n
}
func sozTest(x uint64) (n int) {
	return sovTest(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *A) Marshal() (data []byte, err error) {
	size := m.Size()
	data = make([]byte, size)
	n, err := m.MarshalTo(data)
	if err != nil {
		return nil, err
	}
	return data[:n], nil
}

func (m *A) MarshalTo(data []byte) (n int, err error) {
	var i int
	_ = i
	var l int
	_ = l
	if m.Msg != nil {
		data[i] = 0xa
		i++
		i = encodeVarintTest(data, i, uint64(len(*m.Msg)))
		i += copy(data[i:], *m.Msg)
	}
	if m.XXX_unrecognized != nil {
		i += copy(data[i:], m.XXX_unrecognized)
	}
	return i, nil
}

func encodeFixed64Test(data []byte, offset int, v uint64) int {
	data[offset] = uint8(v)
	data[offset+1] = uint8(v >> 8)
	data[offset+2] = uint8(v >> 16)
	data[offset+3] = uint8(v >> 24)
	data[offset+4] = uint8(v >> 32)
	data[offset+5] = uint8(v >> 40)
	data[offset+6] = uint8(v >> 48)
	data[offset+7] = uint8(v >> 56)
	return offset + 8
}
func encodeFixed32Test(data []byte, offset int, v uint32) int {
	data[offset] = uint8(v)
	data[offset+1] = uint8(v >> 8)
	data[offset+2] = uint8(v >> 16)
	data[offset+3] = uint8(v >> 24)
	return offset + 4
}
func encodeVarintTest(data []byte, offset int, v uint64) int {
	for v >= 1<<7 {
		data[offset] = uint8(v&0x7f | 0x80)
		v >>= 7
		offset++
	}
	data[offset] = uint8(v)
	return offset + 1
}

新的marshal函數要比goprotobuf的proto.Marshal()函數效率高,由於它沒用用reflect接口。性能

11 gogoprotobuf.unmarshaler & gogoprotobuf.unmarshaler_all測試

     當unmarshaler爲true的時候,gogo會爲相應的message生成method:func Unmarshal(data []byte) error,用以代替goprotobuf的proto.Unmarshal([]byte, *struct)函數。ui

pb code:

option (gogoproto.unmarshaler_all) = true;

message A {
	string msg = 1;
}

go code:

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

func (m *A) Unmarshal(data []byte) error {
	l := len(data)
	index := 0
	for index < l {
		var wire uint64
		for shift := uint(0); ; shift += 7 {
			if index >= l {
				return io.ErrUnexpectedEOF
			}
			b := data[index]
			index++
			wire |= (uint64(b) & 0x7F) << shift
			if b < 0x80 {
				break
			}
		}
		fieldNum := int32(wire >> 3)
		wireType := int(wire & 0x7)
		switch fieldNum {
		case 1:
			if wireType != 2 {
				return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType)
			}
			var stringLen uint64
			for shift := uint(0); ; shift += 7 {
				if index >= l {
					return io.ErrUnexpectedEOF
				}
				b := data[index]
				index++
				stringLen |= (uint64(b) & 0x7F) << shift
				if b < 0x80 {
					break
				}
			}
			postIndex := index + int(stringLen)
			if postIndex > l {
				return io.ErrUnexpectedEOF
			}
			s := string(data[index:postIndex])
			m.Msg = &s
			index = postIndex
		default:
			var sizeOfWire int
			for {
				sizeOfWire++
				wire >>= 7
				if wire == 0 {
					break
				}
			}
			index -= sizeOfWire
			skippy, err := github_com_gogo_protobuf_proto.Skip(data[index:])
			if err != nil {
				return err
			}
			if (index + skippy) > l {
				return io.ErrUnexpectedEOF
			}
			m.XXX_unrecognized = append(m.XXX_unrecognized, data[index:index+skippy]...)
			index += skippy
		}
	}
	return nil
}

gogo官方關於這個函數有以下說明:this

Remember when using this code to call proto.Unmarshal. This will call m.Reset and invoke the generated Unmarshal method for you. If you call m.Unmarshal without m.Reset you could be merging protocol buffers.

             from:  https://godoc.org/code.google.com/p/gogoprotobuf/plugin/unmarshal#NewUnmarshal

其意思很明顯,就是m (*A)調用m.Unmarshal方法以前,請先調用m.Reset()函數對其內容進行清空。不然,若是m連續調用了兩次Unmarshal函數,則第二次反序列化出來的內容會被合併到第一次反序列化後的內容以後。

不過,通常狀況下,不多會有人連續兩次調用m.Unmarshal()方法吧?

12 equal & equal_all & verbose_equal & verbose_equal_all

若是設定了equal,則gogo爲每一個message生成一個Equal method。若設定了verbose_equal,則生成VerboseEqual method。Equal與VerboseEqual之間的差異在於,若是兩個對象不等則VerboseEqual返回error不爲nil,這個error會詳細說明兩個對象差異在哪裏,便於debugging。

若是設定了testgen & testgen_all,則gogo會爲這些equal函數生成測試函數。

pb code:
  option (gogoproto.equal_all) = true;
  option (gogoproto.verbose_equal_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 (this *B) VerboseEqual(that interface{}) error {
	if that == nil {
		if this == nil {
			return nil
		}
		return fmt2.Errorf("that == nil && this != nil")
	}

	that1, ok := that.(*B)
	if !ok {
		return fmt2.Errorf("that is not of type *B")
	}
	if that1 == nil {
		if this == nil {
			return nil
		}
		return fmt2.Errorf("that is type *B but is nil && this != nil")
	} else if this == nil {
		return fmt2.Errorf("that is type *B but is not nil && this == nil")
	}
	if !this.A.Equal(&that1.A) {
		return fmt2.Errorf("A this(%v) Not Equal that(%v)", this.A, that1.A)
	}
	if len(this.G) != len(that1.G) {
		return fmt2.Errorf("G this(%v) Not Equal that(%v)", len(this.G), len(that1.G))
	}
	for i := range this.G {
		if !this.G[i].Equal(that1.G[i]) {
			return fmt2.Errorf("G this[%v](%v) Not Equal that[%v](%v)", i, this.G[i], i, that1.G[i])
		}
	}
	if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) {
		return fmt2.Errorf("XXX_unrecognized this(%v) Not Equal that(%v)", this.XXX_unrecognized, that1.XXX_unrecognized)
	}
	return nil
}

func (this *B) Equal(that interface{}) bool {
	if that == nil {
		if this == nil {
			return true
		}
		return false
	}

	that1, ok := that.(*B)
	if !ok {
		return false
	}
	if that1 == nil {
		if this == nil {
			return true
		}
		return false
	} else if this == nil {
		return false
	}
	if !this.A.Equal(&that1.A) {
		return false
	}
	if len(this.G) != len(that1.G) {
		return false
	}
	for i := range this.G {
		if !this.G[i].Equal(that1.G[i]) {
			return false
		}
	}
	if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) {
		return false
	}
	return true
}

13 總結

最後給出一個我的的gogoprotobuf文件樣例:

syntax = "proto3";

package test;

option (gogoproto.gostring_all) = true;
option (gogoproto.equal_all) = true;
option (gogoproto.verbose_equal_all) = true;
// option (gogoproto.goproto_stringer_all) = false;
// option (gogoproto.stringer_all) =  true;
// option (gogoproto.populate_all) = true;
// option (gogoproto.testgen_all) = true;
// option (gogoproto.benchgen_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;

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

enum E {
    option (gogoproto.goproto_enum_prefix) = false;
    EA = 0;
    EB = 2;
}

message A {
	string msg = 1[(gogoproto.nullable) = false];
}

其編譯命令爲:

#!/bin/sh
# proto.sh

# descriptor.proto
gopath=$HOME/bin/go/src
# gogo.proto
gogopath=${gopath}/github.com/gogo/protobuf/protobuf
# Mcommon.proto等號後面的值,用於把test.proto中import "common.proto"生成爲 import "protocol/common"
protoc --proto_path=$gopath:$gogopath:./ --gogo_out=Mcommon.proto=protocol/common:./src/pb test.proto

-----------------------------------

            寫此文檔的時候,得原公司(寫文檔的時候還在公司)同事陶春華兄大力相助, 在此致謝!2016/02/28。                                                          

相關文章
相關標籤/搜索