1. Сериализация буферов протокола
На самом деле процесс кодирования обсуждался в предыдущей статье, а в этой статье на примере golang мы поговорим о процессе сериализации и десериализации с уровня реализации кода.
В качестве примера использования protobuf для сериализации и десериализации данных эта статья начинается с этого примера.
Сначала создайте новый пример сообщения:
syntax = "proto2";
package example;
enum FOO { X = 17; };
message Test {
required string label = 1;
optional int32 type = 2 [default=77];
repeated int64 reps = 3;
optional group OptionalGroup = 4 {
required string RequiredField = 5;
}
}
Используйте protoc-gen-go для создания соответствующих методов get/set. Сгенерированный код можно использовать для сериализации и десериализации в коде.
package main
import (
"log"
"github.com/golang/protobuf/proto"
"path/to/example"
)
func main() {
test := &example.Test {
Label: proto.String("hello"),
Type: proto.Int32(17),
Reps: []int64{1, 2, 3},
Optionalgroup: &example.Test_OptionalGroup {
RequiredField: proto.String("good bye"),
},
}
data, err := proto.Marshal(test)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newTest := &example.Test{}
err = proto.Unmarshal(data, newTest)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
// Now test and newTest contain the same data.
if test.GetLabel() != newTest.GetLabel() {
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
}
// etc.
}
В приведенном выше коде proto.Marshal() — это процесс сериализации. proto.Unmarshal() — это процесс десериализации. В этой главе сначала рассматривается реализация процесса сериализации, а в следующей главе анализируется реализация процесса десериализации.
// Marshal takes the protocol buffer
// and encodes it into the wire format, returning the data.
func Marshal(pb Message) ([]byte, error) {
// Can the object marshal itself?
if m, ok := pb.(Marshaler); ok {
return m.Marshal()
}
p := NewBuffer(nil)
err := p.Marshal(pb)
if p.buf == nil && err == nil {
// Return a non-nil slice on success.
return []byte{}, nil
}
return p.buf, err
}
Как только появится функция сериализации, она сначала вызовет метод сериализации, реализованный самим объектом сообщения.
// Marshaler is the interface representing objects that can marshal themselves.
type Marshaler interface {
Marshal() ([]byte, error)
}
Marshaler — это интерфейс, зарезервированный для пользовательской сериализации объектов. Если есть реализация, верните реализованный вами метод. Если нет, далее выполняется метод сериализации по умолчанию.
p := NewBuffer(nil)
err := p.Marshal(pb)
if p.buf == nil && err == nil {
// Return a non-nil slice on success.
return []byte{}, nil
}
Создайте новый буфер и вызовите его метод Marshal(). После сериализации сообщения поток данных будет помещен в поток байтов buf буфера. Сериализация, наконец, возвращает поток байтов buf.
type Buffer struct {
buf []byte // encode/decode byte stream
index int // read point
// pools of basic types to amortize allocation.
bools []bool
uint32s []uint32
uint64s []uint64
// extra pools, only used with pointer_reflect.go
int32s []int32
int64s []int64
float32s []float32
float64s []float64
}
Структура данных Buffer аналогична приведенной выше. Buffer — это диспетчер буферов для сериализации и десериализации буферов протоколов. Его можно повторно использовать во время вызова, чтобы уменьшить использование памяти. Он поддерживает 7 внутренних пулов, 3 пула базовых типов данных и 4 пула, которые могут использоваться только pointer_reflect.
func (p *Buffer) Marshal(pb Message) error {
// Can the object marshal itself?
if m, ok := pb.(Marshaler); ok {
data, err := m.Marshal()
p.buf = append(p.buf, data...)
return err
}
t, base, err := getbase(pb)
// 异常处理
if structPointer_IsNil(base) {
return ErrNil
}
if err == nil {
err = p.enc_struct(GetProperties(t.Elem()), base)
}
// 用来统计 Encode 次数的
if collectStats {
(stats).Encode++ // Parens are to work around a goimports bug.
}
// maxMarshalSize = 1<<31 - 1,这个值是 protobuf 可以 encoded 的最大值。
if len(p.buf) > maxMarshalSize {
return ErrTooLarge
}
return err
}
Метод Marshal() объекта Buffer по-прежнему определяет, реализует ли объект интерфейс Marshal(). Если да, то пусть он сериализуется, и поток сериализованных двоичных данных добавляется к потоку данных buf.
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
if pb == nil {
err = ErrNil
return
}
// get the reflect type of the pointer to the struct.
t = reflect.TypeOf(pb)
// get the address of the struct.
value := reflect.ValueOf(pb)
b = toStructPointer(value)
return
}
Метод getbase получает тип сообщения и указатель структуры, соответствующий значению, через метод отражения. Получите указатель структуры и сначала выполните обработку исключений.
Таким образом, основной код сериализации на самом деле представляет собой одно предложение, p.enc_struct(GetProperties(t.Elem()), base)
// Encode a struct.
func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error {
var state errorState
// Encode fields in tag order so that decoders may use optimizations
// that depend on the ordering.
// https://developers.google.com/protocol-buffers/docs/encoding#order
for _, i := range prop.order {
p := prop.Prop[i]
if p.enc != nil {
err := p.enc(o, p, base)
if err != nil {
if err == ErrNil {
if p.Required && state.err == nil {
state.err = &RequiredNotSetError{p.Name}
}
} else if err == errRepeatedHasNil {
// Give more context to nil values in repeated fields.
return errors.New("repeated field " + p.OrigName + " has nil element")
} else if !state.shouldContinue(err, p) {
return err
}
}
if len(o.buf) > maxMarshalSize {
return ErrTooLarge
}
}
}
// Do oneof fields.
if prop.oneofMarshaler != nil {
m := structPointer_Interface(base, prop.stype).(Message)
if err := prop.oneofMarshaler(m, o); err == ErrNil {
return errOneofHasNil
} else if err != nil {
return err
}
}
// Add unrecognized fields at the end.
if prop.unrecField.IsValid() {
v := *structPointer_Bytes(base, prop.unrecField)
if len(o.buf)+len(v) > maxMarshalSize {
return ErrTooLarge
}
if len(v) > 0 {
o.buf = append(o.buf, v...)
}
}
return state.err
}
Как вы можете видеть в приведенном выше коде, за исключением одного из полей и нераспознанных полей, они обрабатываются отдельно, и, наконец, другие типы сериализуются путем вызова p.enc(o, p, base).
Структура данных свойств определяется следующим образом:
type Properties struct {
Name string // name of the field, for error messages
OrigName string // original name before protocol compiler (always set)
JSONName string // name to use for JSON; determined by protoc
Wire string
WireType int
Tag int
Required bool
Optional bool
Repeated bool
Packed bool // relevant for repeated primitives only
Enum string // set for enum types only
proto3 bool // whether this is known to be a proto3 field; set for []byte only
oneof bool // whether this is a oneof field
Default string // default value
HasDefault bool // whether an explicit default was provided
CustomType string
StdTime bool
StdDuration bool
enc encoder
valEnc valueEncoder // set for bool and numeric types only
field field
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
tagbuf [8]byte
stype reflect.Type // set for struct types only
sstype reflect.Type // set for slices of structs types only
ctype reflect.Type // set for custom types only
sprop *StructProperties // set for struct types only
isMarshaler bool
isUnmarshaler bool
mtype reflect.Type // set for map types only
mkeyprop *Properties // set for map types only
mvalprop *Properties // set for map types only
size sizer
valSize valueSizer // set for bool and numeric types only
dec decoder
valDec valueDecoder // set for bool and numeric types only
// If this is a packable field, this will be the decoder for the packed version of the field.
packedDec decoder
}
В структуре Properties определены кодировщик с именем enc и декодер с именем dec.
Определения функций кодировщика и декодера абсолютно одинаковы.
type encoder func(p *Buffer, prop *Properties, base structPointer) error
type decoder func(p *Buffer, prop *Properties, base structPointer) error
Функции кодировщика и декодера инициализируются в свойствах:
// Initialize the fields for encoding and decoding.
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
// 下面代码有删减,类似的部分省略了
// proto3 scalar types
case reflect.Int32:
if p.proto3 {
p.enc = (*Buffer).enc_proto3_int32
p.dec = (*Buffer).dec_proto3_int32
p.size = size_proto3_int32
} else {
p.enc = (*Buffer).enc_ref_int32
p.dec = (*Buffer).dec_proto3_int32
p.size = size_ref_int32
}
case reflect.Uint32:
if p.proto3 {
p.enc = (*Buffer).enc_proto3_uint32
p.dec = (*Buffer).dec_proto3_int32 // can reuse
p.size = size_proto3_uint32
} else {
p.enc = (*Buffer).enc_ref_uint32
p.dec = (*Buffer).dec_proto3_int32 // can reuse
p.size = size_ref_uint32
}
case reflect.Float32:
if p.proto3 {
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
p.dec = (*Buffer).dec_proto3_int32
p.size = size_proto3_uint32
} else {
p.enc = (*Buffer).enc_ref_uint32 // can just treat them as bits
p.dec = (*Buffer).dec_proto3_int32
p.size = size_ref_uint32
}
case reflect.String:
if p.proto3 {
p.enc = (*Buffer).enc_proto3_string
p.dec = (*Buffer).dec_proto3_string
p.size = size_proto3_string
} else {
p.enc = (*Buffer).enc_ref_string
p.dec = (*Buffer).dec_proto3_string
p.size = size_ref_string
}
case reflect.Slice:
switch t2 := t1.Elem(); t2.Kind() {
default:
logNoSliceEnc(t1, t2)
break
case reflect.Int32:
if p.Packed {
p.enc = (*Buffer).enc_slice_packed_int32
p.size = size_slice_packed_int32
} else {
p.enc = (*Buffer).enc_slice_int32
p.size = size_slice_int32
}
p.dec = (*Buffer).dec_slice_int32
p.packedDec = (*Buffer).dec_slice_packed_int32
default:
logNoSliceEnc(t1, t2)
break
}
}
case reflect.Map:
p.enc = (*Buffer).enc_new_map
p.dec = (*Buffer).dec_new_map
p.size = size_new_map
p.mtype = t1
p.mkeyprop = &Properties{}
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
p.mvalprop = &Properties{}
vtype := p.mtype.Elem()
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
// The value type is not a message (*T) or bytes ([]byte),
// so we need encoders for the pointer to this type.
vtype = reflect.PtrTo(vtype)
}
p.mvalprop.CustomType = p.CustomType
p.mvalprop.StdDuration = p.StdDuration
p.mvalprop.StdTime = p.StdTime
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
}
p.setTag(lockGetProp)
}
В приведенном выше коде каждый тип перечисляется по регистру переключения, и в каждом случае задаются соответствующие кодировщик кодирования, декодер декодирования и размер. Разница между proto2 и proto3 также разделена на 2 разных случая обработки.
Существуют следующие типы: Reflect.Bool, Reflect.Int32, Reflect.Uint32, Reflect.Int64, Reflect.Uint64, Reflect.Float32, Reflect.Float64, Reflect.String, Reflect.Struct, Reflect.Ptr, Reflect.Slice, Reflect.Map имеет в общей сложности 12 основных категорий.
Следующие в основном выбирают 3 категории: Int32, String, реализация кода карты для анализа.
1. Int32
func (o *Buffer) enc_proto3_int32(p *Properties, base structPointer) error {
v := structPointer_Word32Val(base, p.field)
x := int32(word32Val_Get(v)) // permit sign extension to use full 64-bit range
if x == 0 {
return ErrNil
}
o.buf = append(o.buf, p.tagcode...)
p.valEnc(o, uint64(x))
return nil
}
Обработка кода Int32 относительно проста: сначала поместите код тега в буфер потока двоичных данных buf, затем сериализуйте Int32, а затем поместите код тега в буфер после сериализации.
// EncodeVarint writes a varint-encoded integer to the Buffer.
// This is the format for the
// int32, int64, uint32, uint64, bool, and enum
// protocol buffer types.
func (p *Buffer) EncodeVarint(x uint64) error {
for x >= 1<<7 {
p.buf = append(p.buf, uint8(x&0x7f|0x80))
x >>= 7
}
p.buf = append(p.buf, uint8(x))
return nil
}
Метод обработки кодирования Int32 находится вЧасть 1Как упоминалось внутри, используется метод обработки Varint. Вышеупомянутая функция также работает для int32, int64, uint32, uint64, bool, enum.
Кстати, вы также можете посмотреть конкретную реализацию кода sint32 и Fixed32.
// EncodeZigzag32 writes a zigzag-encoded 32-bit integer
// to the Buffer.
// This is the format used for the sint32 protocol buffer type.
func (p *Buffer) EncodeZigzag32(x uint64) error {
// use signed number to get arithmetic right shift.
return p.EncodeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31))))
}
Для подписанного sint32 это сначала Зигзаг, а затем Варинт.
// EncodeFixed32 writes a 32-bit integer to the Buffer.
// This is the format for the
// fixed32, sfixed32, and float protocol buffer types.
func (p *Buffer) EncodeFixed32(x uint64) error {
p.buf = append(p.buf,
uint8(x),
uint8(x>>8),
uint8(x>>16),
uint8(x>>24))
return nil
}
Для обработки Fixed32 это просто операция смещения, без операции сжатия.
2. String
func (o *Buffer) enc_proto3_string(p *Properties, base structPointer) error {
v := *structPointer_StringVal(base, p.field)
if v == "" {
return ErrNil
}
o.buf = append(o.buf, p.tagcode...)
o.EncodeStringBytes(v)
return nil
}
Сериализация строки также делится на 2 этапа: сначала вставляется код тега, а затем сериализуются данные.
// EncodeStringBytes writes an encoded string to the Buffer.
// This is the format used for the proto2 string type.
func (p *Buffer) EncodeStringBytes(s string) error {
p.EncodeVarint(uint64(len(s)))
p.buf = append(p.buf, s...)
return nil
}
При сериализации строки длина строки сначала записывается в buf путем кодирования Varint. За длиной следует строка. Это также реализация тега — длина — значение.
3. Map
// Encode a map field.
func (o *Buffer) enc_new_map(p *Properties, base structPointer) error {
var state errorState // XXX: or do we need to plumb this through?
v := structPointer_NewAt(base, p.field, p.mtype).Elem() // map[K]V
if v.Len() == 0 {
return nil
}
keycopy, valcopy, keybase, valbase := mapEncodeScratch(p.mtype)
enc := func() error {
if err := p.mkeyprop.enc(o, p.mkeyprop, keybase); err != nil {
return err
}
if err := p.mvalprop.enc(o, p.mvalprop, valbase); err != nil && err != ErrNil {
return err
}
return nil
}
// Don't sort map keys. It is not required by the spec, and C++ doesn't do it.
for _, key := range v.MapKeys() {
val := v.MapIndex(key)
keycopy.Set(key)
valcopy.Set(val)
o.buf = append(o.buf, p.tagcode...)
if err := o.enc_len_thing(enc, &state); err != nil {
return err
}
}
return nil
}
Приведенный выше код также может сериализовать массив словарей, например:
map<key_type, value_type> map_field = N;
Преобразуйте его в соответствующую форму повторяющегося сообщения, а затем сериализуйте.
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
Сериализация карты предназначена для каждого k-v, сначала введите код тега, а затем сериализуйте k-v. Здесь вам нужно вызвать метод enc_len_thing(), когда вам нужно преобразовать структуру неизвестной длины.
// Encode something, preceded by its encoded length (as a varint).
func (o *Buffer) enc_len_thing(enc func() error, state *errorState) error {
iLen := len(o.buf)
o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length
iMsg := len(o.buf)
err := enc()
if err != nil && !state.shouldContinue(err, nil) {
return err
}
lMsg := len(o.buf) - iMsg
lLen := sizeVarint(uint64(lMsg))
switch x := lLen - (iMsg - iLen); {
case x > 0: // actual length is x bytes larger than the space we reserved
// Move msg x bytes right.
o.buf = append(o.buf, zeroes[:x]...)
copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg])
case x < 0: // actual length is x bytes smaller than the space we reserved
// Move msg x bytes left.
copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg])
o.buf = o.buf[:len(o.buf)+x] // x is negative
}
// Encode the length in the reserved space.
o.buf = o.buf[:iLen]
o.EncodeVarint(uint64(lMsg))
o.buf = o.buf[:len(o.buf)+lMsg]
return state.err
}
Метод enc_len_thing() предварительно сохранит 4-байтовое пространство длины. Вычислить длину после сериализации. Если длина превышает 4 байта, сериализованные двоичные данные сдвигаются вправо, а длина заполняется между теговым кодом и данными. Если длина меньше 4 байт, требуется соответствующий сдвиг влево.
4. slice
Наконец, давайте рассмотрим пример массива. Возьмите []int32 в качестве примера.
// Encode a slice of int32s ([]int32) in packed format.
func (o *Buffer) enc_slice_packed_int32(p *Properties, base structPointer) error {
s := structPointer_Word32Slice(base, p.field)
l := s.Len()
if l == 0 {
return ErrNil
}
// TODO: Reuse a Buffer.
buf := NewBuffer(nil)
for i := 0; i < l; i++ {
x := int32(s.Index(i)) // permit sign extension to use full 64-bit range
p.valEnc(buf, uint64(x))
}
o.buf = append(o.buf, p.tagcode...)
o.EncodeVarint(uint64(len(buf.buf)))
o.buf = append(o.buf, buf.buf...)
return nil
}
Сериализуйте этот массив в 3 шага: сначала введите код тега, затем сериализуйте длину всего массива и, наконец, сериализуйте все данные массива сзади. Наконец, формируется форма тега — длина — значение — значение — значение.
Выше приведен процесс сериализации буфера протокола.
Резюме сериализации:
Сериализация буфера протокола использует методы Varint и Zigzag для сжатия целых чисел типа int и целых чисел со знаком. Сжатие чисел с плавающей запятой не выполняется (здесь может быть дальнейшее сжатие, и еще есть возможности для улучшения буфера протокола). кодирование.proto
файле будут проверяться опция и повторяющиеся поля.Если необязательное или повторяющееся поле не задано значением поля, то это поле вообще не существует в сериализованных данных, то есть сериализация выполняться не будет (на одно поле меньше кодируется).
Вышеупомянутые два пункта могут сжимать данные и уменьшать рабочую нагрузку сериализации.
Процесс сериализации представляет собой бинарное смещение, и скорость очень высока. Данные хранятся в бинарном потоке данных в виде тег — длина — значение (или тег — значение). После использования структуры TLV для хранения данных она также избавляется от {, },;
Это делает сериализацию очень быстрой.
2. Десериализация протокольных буферов
Реализация десериализации — это полностью обратный процесс реализации сериализации.
func Unmarshal(buf []byte, pb Message) error {
pb.Reset()
return UnmarshalMerge(buf, pb)
}
Перед началом десериализации сбросьте буфер.
func (p *Buffer) Reset() {
p.buf = p.buf[0:0] // for reading/writing
p.index = 0 // for reading
}
Очистить все данные в buf и сбросить индекс.
func UnmarshalMerge(buf []byte, pb Message) error {
// If the object can unmarshal itself, let it.
if u, ok := pb.(Unmarshaler); ok {
return u.Unmarshal(buf)
}
return NewBuffer(buf).Unmarshal(pb)
}
Старт десериализации данных начинается с вышеописанной функции, если результат пришедшего сообщения не совпадает с результатом buf, конечный результат непредсказуем. Перед десериализацией он также вызовет соответствующий метод Unmarshal(), настроенный под себя.
type Unmarshaler interface {
Unmarshal([]byte) error
}
Unmarshal() — это интерфейс, который вы можете реализовать самостоятельно.
Метод Unmarshal(pb Message) вызывается в UnmarshalMerge.
func (p *Buffer) Unmarshal(pb Message) error {
// If the object can unmarshal itself, let it.
if u, ok := pb.(Unmarshaler); ok {
err := u.Unmarshal(p.buf[p.index:])
p.index = len(p.buf)
return err
}
typ, base, err := getbase(pb)
if err != nil {
return err
}
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
if collectStats {
stats.Decode++
}
return err
}
Unmarshal(pb Message) Эта функция имеет только один входной параметр, который отличается от сигнатуры функции метода proto.Unmarshal() (первая функция имеет только 1 входной параметр, а вторая имеет 2 входных параметра). Разница между ними заключается в том, что реализация функции с одним входным параметром не будет сбрасывать буфер buf, а два входных параметра сначала сбрасывают буфер buf.
Обе эти функции в конечном итоге вызовут метод unmarshalType(), который в конечном итоге будет поддерживать десериализацию.
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
var state errorState
required, reqFields := prop.reqCount, uint64(0)
var err error
for err == nil && o.index < len(o.buf) {
oi := o.index
var u uint64
u, err = o.DecodeVarint()
if err != nil {
break
}
wire := int(u & 0x7)
// 下面代码有省略
dec := p.dec
// 中间代码有省略
decErr := dec(o, p, base)
if decErr != nil && !state.shouldContinue(decErr, p) {
err = decErr
}
if err == nil && p.Required {
// Successfully decoded a required field.
if tag <= 64 {
// use bitmap for fields 1-64 to catch field reuse.
var mask uint64 = 1 << uint64(tag-1)
if reqFields&mask == 0 {
// new required field
reqFields |= mask
required--
}
} else {
// This is imprecise. It can be fooled by a required field
// with a tag > 64 that is encoded twice; that's very rare.
// A fully correct implementation would require allocating
// a data structure, which we would like to avoid.
required--
}
}
}
if err == nil {
if is_group {
return io.ErrUnexpectedEOF
}
if state.err != nil {
return state.err
}
if required > 0 {
// Not enough information to determine the exact field. If we use extra
// CPU, we could determine the field only if the missing required field
// has a tag <= 64 and we check reqFields.
return &RequiredNotSetError{"{Unknown}"}
}
}
return err
}
Функция unmarshalType() относительно длинная и обрабатывает множество ситуаций, включая oneof и WireEndGroup. Функция, которая фактически обрабатывает десериализацию, находится вdecErr := dec(o, p, base)
эта линия.
Функция dec инициализируется в функции setEncAndDec() свойств. Функция упоминалась в сериализации выше, поэтому я не буду повторяться здесь. Функция dec() имеет соответствующую функцию десериализации для каждого отдельного типа.
Точно так же давайте возьмем четыре примера, чтобы увидеть фактическую реализацию десериализации в коде.
1. Int32
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
return nil
}
Десериализация кода Int32 относительно проста, принцип заключается в восстановлении исходных данных в соответствии с процессом, обратным кодированию.
func (p *Buffer) DecodeVarint() (x uint64, err error) {
i := p.index
buf := p.buf
if i >= len(buf) {
return 0, io.ErrUnexpectedEOF
} else if buf[i] < 0x80 {
p.index++
return uint64(buf[i]), nil
} else if len(buf)-i < 10 {
return p.decodeVarintSlow()
}
var b uint64
// we already checked the first byte
x = uint64(buf[i]) - 0x80
i++
b = uint64(buf[i])
i++
x += b << 7
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 7
b = uint64(buf[i])
i++
x += b << 14
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 14
b = uint64(buf[i])
i++
x += b << 21
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 21
b = uint64(buf[i])
i++
x += b << 28
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 28
b = uint64(buf[i])
i++
x += b << 35
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 35
b = uint64(buf[i])
i++
x += b << 42
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 42
b = uint64(buf[i])
i++
x += b << 49
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 49
b = uint64(buf[i])
i++
x += b << 56
if b&0x80 == 0 {
goto done
}
x -= 0x80 << 56
b = uint64(buf[i])
i++
x += b << 63
if b&0x80 == 0 {
goto done
}
// x -= 0x80 << 63 // Always zero.
return 0, errOverflow
done:
p.index = i
return x, nil
}
После сериализации Int32 первый байт должен быть 0x80, затем после удаления этого байта каждый последующий двоичный байт — это данные, а оставшийся шаг — сложение каждого числа посредством операции смещения. Приведенная выше функция десериализации также работает для int32, int64, uint32, uint64, bool и enum.
Кстати, вы также можете взглянуть на конкретную реализацию кода десериализации sint32 и Fixed32.
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
x, err = p.DecodeVarint()
if err != nil {
return
}
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
return
}
Для подписанного sint32 процесс десериализации состоит в том, чтобы сначала десериализовать Varint, а затем десериализовать Zigzag.
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
// x, err already 0
i := p.index + 4
if i < 0 || i > len(p.buf) {
err = io.ErrUnexpectedEOF
return
}
p.index = i
x = uint64(p.buf[i-4])
x |= uint64(p.buf[i-3]) << 8
x |= uint64(p.buf[i-2]) << 16
x |= uint64(p.buf[i-1]) << 24
return
}
Процесс десериализации fixed32 также осуществляется посредством смещения, содержимое каждого байта накапливается, и исходные данные могут быть восстановлены. Обратите внимание, что здесь также пропущено расположение тега.
2. String
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
n, err := p.DecodeVarint()
if err != nil {
return nil, err
}
nb := int(n)
if nb < 0 {
return nil, fmt.Errorf("proto: bad byte length %d", nb)
}
end := p.index + nb
if end < p.index || end > len(p.buf) {
return nil, io.ErrUnexpectedEOF
}
if !alloc {
// todo: check if can get more uses of alloc=false
buf = p.buf[p.index:end]
p.index += nb
return
}
buf = make([]byte, nb)
copy(buf, p.buf[p.index:])
p.index += nb
return
}
Чтобы десериализовать строку, сначала сериализуйте длину и используйте метод DecodeVarint. После получения длины все остальное — процесс прямого копирования. В последней части кодирования мы знаем, что строка не обрабатывается и напрямую помещается в двоичный поток, поэтому десериализацию можно выполнять напрямую.
3. Map
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
raw, err := o.DecodeRawBytes(false)
if err != nil {
return err
}
oi := o.index // index at the end of this map entry
o.index -= len(raw) // move buffer back to start of map entry
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
if mptr.Elem().IsNil() {
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
}
v := mptr.Elem() // map[K]V
// 这里省略一些代码,主要是为了 key - value 准备的一些可以双重间接寻址的占位符,具体原因可以见序列化代码里面的 enc_new_map 函数
// Decode.
// This parses a restricted wire format, namely the encoding of a message
// with two fields. See enc_new_map for the format.
for o.index < oi {
// tagcode for key and value properties are always a single byte
// because they have tags 1 and 2.
tagcode := o.buf[o.index]
o.index++
switch tagcode {
case p.mkeyprop.tagcode[0]:
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
return err
}
case p.mvalprop.tagcode[0]:
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
return err
}
default:
// TODO: Should we silently skip this instead?
return fmt.Errorf("proto: bad map data tag %d", raw[0])
}
}
keyelem, valelem := keyptr.Elem(), valptr.Elem()
if !keyelem.IsValid() {
keyelem = reflect.Zero(p.mtype.Key())
}
if !valelem.IsValid() {
valelem = reflect.Zero(p.mtype.Elem())
}
v.SetMapIndex(keyelem, valelem)
return nil
}
Десериализация карты требует удаления каждого тега, а затем десериализации каждой пары "ключ-значение". Наконец, будет определено, являются ли значения keyelem и valelem нулевыми.
4. slice
Наконец, давайте рассмотрим пример массива. Возьмите []int32 в качестве примера.
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
v := structPointer_Word32Slice(base, p.field)
nn, err := o.DecodeVarint()
if err != nil {
return err
}
nb := int(nn) // number of bytes of encoded int32s
fin := o.index + nb
if fin < o.index {
return errOverflow
}
for o.index < fin {
u, err := p.valDec(o)
if err != nil {
return err
}
v.Append(uint32(u))
}
return nil
}
Десериализуйте этот массив в 2 шага, пропустите код тега, чтобы получить длину, и десериализуйте длину. Десериализуйте каждое значение по очереди по длине этой длины.
Выше описан процесс десериализации буфера протокола.
Резюме десериализации:
Десериализация буфера протокола напрямую считывает двоичный поток байтовых данных. Десериализация - это процесс, обратный кодированию, который также представляет собой некоторые бинарные операции. При десериализации вам обычно нужно использовать только длину. Значение тега используется только для идентификации типа.Декодер декодирования, соответствующий каждому типу, был инициализирован в методе свойств setEncAndDec(), поэтому при десериализации значение тега может быть пропущено напрямую и обработано по длине.
Процесс синтаксического анализа XML немного сложнее. XML необходимо прочитать строку из файла и преобразовать ее в модель объектной структуры документа XML. После этого прочитайте строку указанного узла из модели структуры объекта XML-документа и, наконец, преобразуйте строку в переменную указанного типа. Этот процесс очень сложен, поскольку процесс преобразования XML-файлов в модель объектной структуры документа часто требует сложных вычислений с интенсивным использованием ЦП, таких как анализ лексической грамматики.
3. Производительность сериализации/десериализации
Буферы протоколов всегда считались высокопроизводительными. Есть также много людей, которые сделали это и подтвердили это утверждение. Например эксперимент по этой ссылкеjvm-serializers.
Прежде чем смотреть на данные, мы можем рационально проанализировать преимущества Protocol Buffer по сравнению с JSON и XML:
- Protobuf использует Varint и Zigzag для сильного сжатия целочисленного типа, а в JSON нет {, }, ;, разделителей данных, а поле option идентифицируется, когда данных нет, они не будут десериализованы. Эти меры приводят к тому, что общий объем данных pb намного меньше, чем JSON.
- Protobuf принимает форму TLV, JSON — все это в виде строк. Сравнение строк должно занимать больше времени, чем числовые теги полей. Protobuf имеет маркер размера или длины перед телом, в то время как JSON должен сканироваться в полном тексте и не может пропускать ненужные поля.
Следующее изображение взято из справочной ссылки «Protobuf в 5 раз быстрее, чем JSON? Используйте код, чтобы разрушить миф о производительности pb":
Из этого эксперимента следует, что Protobuf очень мощен в сериализации чисел.
Сериализация/десериализация чисел действительно является преимуществом Protobuf по сравнению с JSON и XML, но у него также есть некоторые области, в которых он не работает. такие как струны. Строки в основном не обрабатываются в Protobuf, за исключением того, что впереди добавляется тег длины. В процессе сериализации/десериализации строк скорость копирования строк определяет реальную скорость.
Как видно из рисунка выше, при кодировании строки скорость в основном такая же, как и у JSON.
3. Наконец
К этому моменту читатель должен знать все о буферах протоколов.
В начале своего рождения буферы протоколов существовали не для передачи данных, а для решения проблемы многоверсионной совместимости протоколов серверов. Суть, собственно, в том, чтобы придумать новый кросс-языковой однозначный IDL (язык описания интерфейсов). Просто люди позже узнали, что использовать его для передачи данных тоже хорошо, и стали использовать протокольные буферы.
Хотите заменить JSON буферами протокола, вероятно, учитывая:
- Те же данные, что и буферы протокола, объем передаваемых данных меньше, чем у JSON.После сжатия gzip или 7zip передача по сети потребляет меньше.
- Буферы протоколов не являются самоописываемыми, в отсутствие
.proto
После файла идет определенная степень шифрования, а процесс передачи данных представляет собой двоичный поток, а не открытый текст. - Буферы протоколов предоставляют набор инструментов, а также очень удобно автоматизировать генерацию кода.
- Буферы протокола обратно совместимы, изменение структуры данных не влияет на более старые версии.
- Буферы протоколов изначально полностью совместимы с вызовами RPC.
Если целые числа, числа с плавающей запятой и все строковые данные используются редко, то буферы JSON и протоколов работают ненамного хуже. Для чисто интерфейсных взаимодействий выбор JSON или буферов протокола не сильно отличается.
В процессе взаимодействия с бэкендом используется множество протокольных буферов, и автор считает, что выбор протокольных буферов не только силен в производительности, но и является важным фактором идеальной совместимости с RPC-вызовами.
Ссылка:
официальная документация гугл
thrift-protobuf-compare - Benchmarking.wiki
jvm-serializers
Protobuf в 5 раз быстрее, чем JSON? Использование кода для развенчания мифа о производительности pb
Репозиторий GitHub:Halfrost-Field
Follow: halfrost · GitHub
Source: HAL frost.com/proto часть _…