protobuf原理解析-基于protobuf-c实现序列化,反向序列化

1.一个实例
前面介绍了使用protobuf的流程.
(1). 定义proto文件来描述需要序列化和反向序列化传输的消息.
(2). 借助proto-c,为proto文件生成对应的代码控制文件.
(3). 程序借助生成的代码控制文件和protobuf-c动态库的支持实现类型序列化,反向序列化.

我们以一个实例来分析protobuf-c中序列化,反向序列化的处理.
a. proto文件为test_normal.proto

syntax = "proto2";
package foo;enum TestEnum {VALUENEG123456 = -123456;VALUENEG1 = -1;VALUE0 = 0;VALUE2097152 = 2097152;VALUE268435456 = 268435456;
}message TestInt{optional int32 test_int = 1;optional int32 test_int2 = 2 [default = 100];
}message TestClass {optional int32 test_int32 = 1;optional sint32 test_sint32 = 2;optional sfixed32 test_sfixed32 = 3;optional int64 test_int64 = 4;optional sint64 test_sint64 = 5;optional sfixed64 test_sfixed64 = 6;optional uint32 test_uint32 = 7;optional fixed32 test_fixed32 = 8;optional uint64 test_uint64 = 9;optional fixed64 test_fixed64 = 10;optional float test_float = 11;optional double test_double = 12;optional bool test_boolean = 13;optional string test_string = 14;optional bytes test_bytes = 15;optional TestEnum test_enum = 16;optional TestInt test_class = 17;required int32 test_int32_req = 101;required sint32 test_sint32_req = 102;required sfixed32 test_sfixed32_req = 103;required int64 test_int64_req = 104;required sint64 test_sint64_req = 105;required sfixed64 test_sfixed64_req = 106;required uint32 test_uint32_req = 107;required fixed32 test_fixed32_req = 108;required uint64 test_uint64_req = 109;required fixed64 test_fixed64_req = 110;required float test_float_req = 111;required double test_double_req = 112;required bool test_boolean_req = 113;required string test_string_req = 114;required bytes test_bytes_req = 115;required TestEnum test_enum_req = 116;required TestInt test_class_req = 117;repeated int32 test_int32_rep = 201;repeated sint32 test_sint32_rep = 202;repeated sfixed32 test_sfixed32_rep = 203;repeated int64 test_int64_rep = 204;repeated sint64 test_sint64_rep = 205;repeated sfixed64 test_sfixed64_rep = 206;repeated uint32 test_uint32_rep = 207;repeated fixed32 test_fixed32_rep = 208;repeated uint64 test_uint64_rep = 209;repeated fixed64 test_fixed64_rep = 210;repeated float test_float_rep = 211;repeated double test_double_rep = 212;repeated bool test_boolean_rep = 213;repeated string test_string_rep = 214;repeated bytes test_bytes_rep = 215;repeated TestEnum test_enum_rep = 216;repeated TestInt test_class_rep = 217;repeated int32 test_int32_rep_p = 301 [packed=true];repeated sint32 test_sint32_rep_p = 302 [packed=true];repeated sfixed32 test_sfixed32_rep_p = 303 [packed=true];repeated int64 test_int64_rep_p = 304 [packed=true];repeated sint64 test_sint64_rep_p = 305 [packed=true];repeated sfixed64 test_sfixed64_rep_p = 306 [packed=true];repeated uint32 test_uint32_rep_p = 307 [packed=true];repeated fixed32 test_fixed32_rep_p = 308 [packed=true];repeated uint64 test_uint64_rep_p = 309 [packed=true];repeated fixed64 test_fixed64_rep_p = 310 [packed=true];repeated float test_float_rep_p = 311 [packed=true];repeated double test_double_rep_p = 312 [packed=true];repeated bool test_boolean_rep_p = 313 [packed=true];repeated TestEnum test_enum_rep_p = 316 [packed=true];
}

b.生成的代码控制文件为test_normal.pb-c.htest_normal.pb-c.c
内容略去.

c.主程序为main.c

#include "test_normal.pb-c.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>void checkValid(Foo__TestClass *p1, Foo__TestClass* p2){if(p1->has_test_int32 != p2->has_test_int32 || p1->test_int32 != p2->test_int32) {printf("check test_int32 err\n");return;}// 后续检测略去
}int main(void)
{Foo__TestClass stTest = FOO__TEST_CLASS__INIT;// optional部分stTest.has_test_int32 = 1;stTest.test_int32 = 1;// 后续各个字段赋值略去int64_t nPackSize = foo__test_class__get_packed_size(&stTest);printf("nPackSize_%d\n", nPackSize);uint8_t* pBuff = (uint8_t*)malloc(nPackSize);int64_t nRealPackSize = foo__test_class__pack(&stTest, pBuff);printf("nRealPackSize_%d\n", nRealPackSize);unsigned char simple_pad[8];ProtobufCBufferSimple bs = PROTOBUF_C_BUFFER_SIMPLE_INIT (simple_pad);foo__test_class__pack_to_buffer(&stTest, &bs.base);printf("bs.len_%d\n", bs.len);int32_t nCmp = memcmp(bs.data, pBuff, bs.len);printf("nCmp_%d\n", nCmp);PROTOBUF_C_BUFFER_SIMPLE_CLEAR (&bs);Foo__TestClass* pTestClass = NULL;pTestClass = foo__test_class__unpack(NULL, nRealPackSize, pBuff);checkValid(pTestClass, &stTest);foo__test_class__free_unpacked(pTestClass, NULL);free (pBuff);return 0;
}

编译并运行输出如下:
在这里插入图片描述
其中nCmp_0后续的输出是checkValid中打印的内容.

2.计算类型实例序列化尺寸
我们分析针对自定义类型计算类型实例序列化尺寸的过程.

size_t protobuf_c_message_get_packed_size(const ProtobufCMessage *message)
{unsigned i;size_t rv = 0;for (i = 0; i < message->descriptor->n_fields; i++) {const ProtobufCFieldDescriptor *field = message->descriptor->fields + i;const void *member = ((const char *) message) + field->offset;const void *qmember = ((const char *) message) + field->quantifier_offset;if (field->label == PROTOBUF_C_LABEL_REQUIRED) {rv += required_field_get_packed_size(field, member);} else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {rv += optional_field_get_packed_size(field, *(protobuf_c_boolean *) qmember, member);} else {rv += repeated_field_get_packed_size(field, *(const size_t *) qmember, member);}}return rv;
}

我们不考虑oneof类型,unknown field的情况下.计算类型实例序列化尺寸的过程可分解为依次对类型的每个字段求取序列化尺寸.所有字段序列化尺寸之和就是类型实例的序列化尺寸.

字段按其修饰符类型可以分为required,optional,repeated三种类型.我们分别分析每种类型下字段序列化尺寸计算方式.
(1). required

rv += required_field_get_packed_size(field, member);

该类型字段序列化由两部分构成:
在这里插入图片描述
a. tag用来存储字段的序号信息.不同序号数值及其所需尺寸关系为:

static inline size_t get_tag_size(uint32_t number) {if (number < (1UL << 4)) {return 1;} else if (number < (1UL << 11)) {return 2;} else if (number < (1UL << 18)) {return 3;} else if (number < (1UL << 25)) {return 4;} else {return 5;}
}

为何tag部分尺寸和序号数值存在上述关系.可参考tag部分序列化处理后,得到解释.
b. data
data部分所需序列化尺寸不同类型计算方式不同.
b.1.PROTOBUF_C_TYPE_SINT32

uint32_size(zigzag32(v));
static inline uint32_t zigzag32(int32_t v) {return ((uint32_t)v << 1) ^ -((uint32_t)v >> 31);
}

即先经过zigzag32处理得到一个uint32_t数值,再对其按uint32_size计算尺寸.
zigzag32处理效果如下:
在这里插入图片描述
即将一个有符号数,转化为相同尺寸下一个无符号数.
b.2.PROTOBUF_C_TYPE_ENUM
b.3.PROTOBUF_C_TYPE_INT32

static inline size_t int32_size(int32_t v) {if (v < 0) {return 10;} else if (v < (1L << 7)) {return 1;} else if (v < (1L << 14)) {return 2;} else if (v < (1L << 21)) {return 3;} else if (v < (1L << 28)) {return 4;} else {return 5;}
}

为何数值和所需序列化尺寸存在上述关系,需结合后续序列化处理过程分析.
b.4.PROTOBUF_C_TYPE_UINT32

static inline size_t uint32_size(uint32_t v) {if (v < (1UL << 7)) {return 1;} else if (v < (1UL << 14)) {return 2;} else if (v < (1UL << 21)) {return 3;} else if (v < (1UL << 28)) {return 4;} else {return 5;}
}

为何数值与所需序列化尺寸存在上述关系,需结合后续序列化处理过程分析.
b.5.PROTOBUF_C_TYPE_SINT64

uint64_size(zigzag64(v));
static inline uint64_t zigzag64(int64_t v) {return ((uint64_t)v << 1) ^ -((uint64_t)v >> 63);
}

zigzag64可以参考zigzag32,处理的目的是将一个64位有符号数,转化为一个64位无符号数.
b.6. PROTOBUF_C_TYPE_INT64
b.7. PROTOBUF_C_TYPE_UINT64

static inline size_t uint64_size(uint64_t v) {uint32_t upper_v = (uint32_t) (v >> 32);if (upper_v == 0) {return uint32_size((uint32_t) v);} else if (upper_v < (1UL << 3)) {return 5;} else if (upper_v < (1UL << 10)) {return 6;} else if (upper_v < (1UL << 17)) {return 7;} else if (upper_v < (1UL << 24)) {return 8;} else if (upper_v < (1UL << 31)) {return 9;} else {return 10;}
}

为何数值与所需序列化尺寸存在上述关系,需结合后续序列化处理过程分析.
b.8. PROTOBUF_C_TYPE_SFIXED32
b.9. PROTOBUF_C_TYPE_FIXED32
序列化尺寸固定为:
b.10. PROTOBUF_C_TYPE_SFIXED64
b.11. PROTOBUF_C_TYPE_FIXED64
序列化尺寸固定为:
b.12. PROTOBUF_C_TYPE_BOOL
序列化尺寸固定为:
b.13. PROTOBUF_C_TYPE_FLOAT
序列化尺寸固定为:
b.14. PROTOBUF_C_TYPE_DOUBLE
序列化尺寸固定为:
b.15. PROTOBUF_C_TYPE_STRING
先计算字符串长度len,按uint32_size(len)计算长度序列化尺寸,再加上len,就是其序列化尺寸.
b.16. PROTOBUF_C_TYPE_BYTES
uint32_size(len)计算长度序列化尺寸,再加上len,就是其序列化尺寸.
b.17. PROTOBUF_C_TYPE_MESSAGE
假设自定义类型实例字段序列化所需尺寸为len,则uint32_size(len),再加上len就是其序列化尺寸.

(2). optional

static size_t optional_field_get_packed_size(const ProtobufCFieldDescriptor *field, const protobuf_c_boolean has, const void *member)
{if (field->type == PROTOBUF_C_TYPE_MESSAGE || field->type == PROTOBUF_C_TYPE_STRING){const void *ptr = *(const void * const *) member;if (ptr == NULL || ptr == field->default_value)return 0;} else {if (!has)return 0;}return required_field_get_packed_size(field, member);
}

该类型字段序列化尺寸计算方式简要描述为:
a. 针对PROTOBUF_C_TYPE_MESSAGEPROTOBUF_C_TYPE_STRING类型字段,当其数值为null或默认值时,所需尺寸为0
b. 针对其他类型,其关联has字段为0时,所需尺寸为0
c. 其他情况下,按required方式计算所需序列化尺寸.

(3). repeated
首先,若字段的数量为0,则所需尺寸为0
字段数量不为0时,可分为unpack方式,pack方式.
两种方式下,我们将其序列化尺寸分为序号部分,数据部分.两者数据部分尺寸计算方式一致.序号部分不同.

序号部分尺寸计算:
a. unpack方式
a.1.依据序号计算序号所需尺寸

static inline size_t get_tag_size(uint32_t number) {if (number < (1UL << 4)) {return 1;} else if (number < (1UL << 11)) {return 2;} else if (number < (1UL << 18)) {return 3;} else if (number < (1UL << 25)) {return 4;} else {return 5;}
}

a.2. 序号总尺寸
按上述计算出的序号尺寸 * 元素数量.
b. pack方式
b.1. 依据序号计算序号所需尺寸
计算方式参考a.1
b.2. 序号总尺寸
假设所有元素数据部分所需尺寸为len,则总尺寸= 按上述计算的序号尺寸+uint32_size(len)

数据部分尺寸计算:
元素类型不同计算方式也不同.
a.1. PROTOBUF_C_TYPE_SINT32

sint32_size(((int32_t *) array)[i])

对每个元素分别按上述计算尺寸.
a.2. PROTOBUF_C_TYPE_ENUM
a.3. PROTOBUF_C_TYPE_INT32

int32_size(((int32_t *) array)[i]);

对每个元素分别按上述计算尺寸.
a.4. PROTOBUF_C_TYPE_UINT32

uint32_size(((uint32_t *) array)[i]);

对每个元素分别按上述计算尺寸.
a.5. PROTOBUF_C_TYPE_SINT64

sint64_size(((int64_t *) array)[i]);

对每个元素分别按上述计算尺寸.
a.6. PROTOBUF_C_TYPE_INT64
a.7. PROTOBUF_C_TYPE_UINT64

uint64_size(((uint64_t *) array)[i]);

对每个元素分别按上述计算尺寸.
a.8. PROTOBUF_C_TYPE_SFIXED32
a.9. PROTOBUF_C_TYPE_FIXED32
a.10. PROTOBUF_C_TYPE_FLOAT
每个元素固定尺寸为:
a.11. PROTOBUF_C_TYPE_SFIXED64
a.12. PROTOBUF_C_TYPE_FIXED64
a.13. PROTOBUF_C_TYPE_DOUBLE
每个元素固定尺寸为:
a.14. PROTOBUF_C_TYPE_BOOL
每个元素固定尺寸为:
a.15. PROTOBUF_C_TYPE_STRING
假设当前元素字符串长度为len,则其尺寸为uint32_size(len) + len;
a.16. PROTOBUF_C_TYPE_BYTES
假设当前元素长度为len,则其尺寸为uint32_size(len) + len;
a.17. PROTOBUF_C_TYPE_MESSAGE
假设当前自定义类型实例序列化尺寸为len,则其所需尺寸为uint32_size(len) + len;

每个元素的尺寸累计起来就是数据部分序列化所需尺寸.

3.将类型实例序列化并输出到指定缓存区

size_t protobuf_c_message_pack(const ProtobufCMessage *message, uint8_t *out)
{unsigned i;size_t rv = 0;ASSERT_IS_MESSAGE(message);for (i = 0; i < message->descriptor->n_fields; i++) {const ProtobufCFieldDescriptor *field = message->descriptor->fields + i;const void *member = ((const char *) message) + field->offset;const void *qmember = ((const char *) message) + field->quantifier_offset;if (field->label == PROTOBUF_C_LABEL_REQUIRED) {rv += required_field_pack(field, member, out + rv);} else if (field->label == PROTOBUF_C_LABEL_OPTIONAL) {rv += optional_field_pack(field, *(const protobuf_c_boolean *) qmember, member, out + rv);} else {rv += repeated_field_pack(field, *(const size_t *) qmember, member, out + rv);}}return rv;
}

我们忽略oneof,unknown field的情况.上述过程是针对类型实例的每个字段数据逐个序列化的过程.
3.1.required字段序列化
a. 序号序列化
首先针对字段序号进行序列化

static size_t tag_pack(uint32_t id, uint8_t *out)
{if (id < (1UL << (32 - 3)))return uint32_pack(id << 3, out);elsereturn uint64_pack(((uint64_t) id) << 3, out);
}

因为序号序列化的低三个比特位需作其他用途.
所以,在判断id数值可用其低29个比特位表示时,构造一个高29个比特位为id数值,低三个比特位为0的数值,再序列化.否则,构造一个高61个比特位为id数值,低三个比特位为0的数值,再序列化.

static inline size_t uint32_pack(uint32_t value, uint8_t *out)
{unsigned rv = 0;if (value >= 0x80) {out[rv++] = value | 0x80;value >>= 7;if (value >= 0x80) {out[rv++] = value | 0x80;value >>= 7;if (value >= 0x80) {out[rv++] = value | 0x80;value >>= 7;if (value >= 0x80) {out[rv++] = value | 0x80;value >>= 7;}}}}out[rv++] = value;return rv;
}

上述编码有个特定名称:Varints编码.用一个实例来说明其对无符号数值的编码过程.

uint32_t val = 666;
补码:000 ... 101 0011010  // 666 的源码
Varints 编码:1#0011010  0#000 01019a 05// 666 的 Varints 编码

其编码过程是依次取数值低7位,构造一个字节,直到处理了数值全部有效位.非最后一个字节最高位需设置为1.知道了序号的序列化规则,也就能解释前面序号数值与其所需序列化尺寸的关系计算了.

uint64_pack过程类似uint32_pack不再额外分析.
这样处理序号序列化时,首个字节低三位必然是0.我们可以用这三个比特位来存储额外信息.

b.数值序列化
数值序列化需结合数值类型.
b.1. PROTOBUF_C_TYPE_SINT32
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

sint32_pack(*(const int32_t *) member, out + rv)
static inline size_t sint32_pack(int32_t value, uint8_t *out)
{return uint32_pack(zigzag32(value), out);
}

其序列化过程是先通过zigzag32int32_t数值变换为等价的uint32_t数值,再对变换后数值执行uint32_pack.这个处理已经分析过了.
b.2.PROTOBUF_C_TYPE_ENUM
b.3.PROTOBUF_C_TYPE_INT32
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

int32_pack(*(const int32_t *) member, out + rv);
static inline size_t int32_pack(uint32_t value, uint8_t *out) {if ((int32_t)value < 0) {out[0] = value | 0x80;out[1] = (value >> 7) | 0x80;out[2] = (value >> 14) | 0x80;out[3] = (value >> 21) | 0x80;out[4] = (value >> 28) | 0xf0;out[5] = out[6] = out[7] = out[8] = 0xff;out[9] = 0x01;return 10;} else {return uint32_pack(value, out);}
}

上述的处理中当要编码的数值大于0时,我们按uint32_pack对其执行Varints编码即可.但在其数值为负数时,上述首先将一个32比特位的负数的补码扩展到64比特位下此负数的补码,然后按Varints编码规则,依次取低7个比特位构成一个字节,作为序列化结果.直到处理完所有有效位.负数补码下,最高位必然是1,所以需10个字节完成序列化,然后非最后一个字节需将最高比特位设置为1
b.4.PROTOBUF_C_TYPE_UINT32
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

uint32_pack(*(const uint32_t *) member, out + rv);

b.5.PROTOBUF_C_TYPE_SINT64
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

sint64_pack(*(const int64_t *) member, out + rv);
static inline size_t sint64_pack(int64_t value, uint8_t *out)
{return uint64_pack(zigzag64(value), out);
}

数值编码时,先按zigzag将有符号有符号数值转化为一个无符号64位数值,再对其执行Varints编码.
b.6.PROTOBUF_C_TYPE_INT64
b.7.PROTOBUF_C_TYPE_UINT64
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

uint64_pack(*(const uint64_t *) member, out + rv);
static size_t uint64_pack(uint64_t value, uint8_t *out)
{uint32_t hi = (uint32_t) (value >> 32);uint32_t lo = (uint32_t) value;unsigned rv;if (hi == 0)return uint32_pack((uint32_t) lo, out);out[0] = (lo) | 0x80;out[1] = (lo >> 7) | 0x80;out[2] = (lo >> 14) | 0x80;out[3] = (lo >> 21) | 0x80;if (hi < 8) {out[4] = (hi << 4) | (lo >> 28);return 5;} else {out[4] = ((hi & 7) << 4) | (lo >> 28) | 0x80;hi >>= 3;}rv = 5;while (hi >= 128) {out[rv++] = hi | 0x80;hi >>= 7;}out[rv++] = hi;return rv;
}

上述过程依然是对数值按二进制展开后,逐个取低7个比特位构成一个字节,直到处理完毕所有有效比特位.且设置除最后一个字节外的其他字节最高位为1Varints编码过程.采用上述过程处理64位负数时,由于未采用zigzag,故编码会消耗较多字节(10字节).
b.8. PROTOBUF_C_TYPE_SFIXED32
b.9. PROTOBUF_C_TYPE_FIXED32
b.10. PROTOBUF_C_TYPE_FLOAT
首先将序号序列化首字节低三位设置为101.这表示我们会对此字段数值采用32BIT编码来执行序列化.

fixed32_pack(*(const uint32_t *) member, out + rv);
static inline size_t fixed32_pack(uint32_t value, void *out)
{
#if !defined(WORDS_BIGENDIAN)memcpy(out, &value, 4);
#elseuint8_t *buf = out;buf[0] = value;// 低8比特位buf[1] = value >> 8;// 次低8比特位buf[2] = value >> 16;// 次次低8比特位buf[3] = value >> 24;// 最高8比特位
#endifreturn 4;
}

即对一个32比特位数值,依次提取低8比特位构成一个输出字节的方式进行序列化.
b.11. PROTOBUF_C_TYPE_SFIXED64
b.12. PROTOBUF_C_TYPE_FIXED64
b.13. PROTOBUF_C_TYPE_DOUBLE
首先将序号序列化首字节低三位设置为001.这表示我们会对此字段数值采用64BIT编码来执行序列化.

fixed64_pack(*(const uint64_t *) member, out + rv);
static inline size_t fixed64_pack(uint64_t value, void *out)
{
#if !defined(WORDS_BIGENDIAN)memcpy(out, &value, 8);
#elsefixed32_pack(value, out);fixed32_pack(value >> 32, ((char *) out) + 4);
#endifreturn 8;
}

即对一个64比特位数值,依次提取低8比特位构成一个输出字节的方式进行序列化.
b.14. PROTOBUF_C_TYPE_BOOL
首先将序号序列化首字节低三位设置为000.这表示我们会对此字段数值采用Varints编码来执行序列化.

boolean_pack(*(const protobuf_c_boolean *) member, out + rv);
static inline size_t boolean_pack(protobuf_c_boolean value, uint8_t *out)
{*out = value ? TRUE : FALSE;return 1;
}

由于bool作为数值只有0,1两种可能.所以上述符合Varints编码对数值的处理.
b.15. PROTOBUF_C_TYPE_STRING
首先将序号序列化首字节低三位设置为010.这表示我们会对此字段数值采用LENGTH_PREFIXED编码来执行序列化.

string_pack(*(char *const *) member, out + rv);
static inline size_t string_pack(const char *str, uint8_t *out)
{if (str == NULL) {out[0] = 0;return 1;} else {size_t len = strlen(str);size_t rv = uint32_pack(len, out);memcpy(out + rv, str, len);return rv + len;}
}

即数值为空指针时,固定占一个字节.指向有效字符串时,先按uint32_pack(len, out)序列化字符串长度,在序列化存储字符串内容.
b.16. PROTOBUF_C_TYPE_BYTES
首先将序号序列化首字节低三位设置为010.这表示我们会对此字段数值采用LENGTH_PREFIXED编码来执行序列化.

binary_data_pack((const ProtobufCBinaryData *) member, out + rv);
static inline size_t binary_data_pack(const ProtobufCBinaryData *bd, uint8_t *out)
{size_t len = bd->len;size_t rv = uint32_pack(len, out);memcpy(out + rv, bd->data, len);return rv + len;
}

即先通过uint32_pack(len, out);序列化尺寸尺寸信息,再存储比特流内容部分.
b.17. PROTOBUF_C_TYPE_MESSAGE
首先将序号序列化首字节低三位设置为010.这表示我们会对此字段数值采用LENGTH_PREFIXED编码来执行序列化.

prefixed_message_pack(*(ProtobufCMessage * const *) member, out + rv);
static inline size_t prefixed_message_pack(const ProtobufCMessage *message, uint8_t *out)
{if (message == NULL) {out[0] = 0;return 1;} else {size_t rv = protobuf_c_message_pack(message, out + 1);uint32_t rv_packed_size = uint32_size(rv);if (rv_packed_size != 1)memmove(out + rv_packed_size, out + 1, rv);return uint32_pack(rv, out) + rv;}
}

上述是一个递归定义,对字段也是自定义类型的情况.先序列化尺寸信息,再序列化字段内容.

3.2. optional类型字段序列化

static size_t optional_field_pack(const ProtobufCFieldDescriptor *field, const protobuf_c_boolean has, const void *member, uint8_t *out)
{if (field->type == PROTOBUF_C_TYPE_MESSAGE || field->type == PROTOBUF_C_TYPE_STRING){const void *ptr = *(const void * const *) member;if (ptr == NULL || ptr == field->default_value)return 0;} else {if (!has)return 0;}return required_field_pack(field, member, out);
}

当字段类型为MESSAGESTRING时,若字段数值部分为空指针或为字段默认值.不占用序列化空间.
其他类型字段,当其关联has字段为false时,也不占用序列化空间.
其他情况,按required类型序列化字段数据部分即可.

3.3.repeated类型字段序列化
我们进一步分别考察packed方式下序列化,unpacked方式下序列化.
(1). packed方式下序列化
a. 元素数量为0时,不占序列化空间.
b. 对字段序号按执行序列化.

tag_pack(field->id, out);

tag_pack之前分析过.是先对数值左移三位,再对新数值执行varint编码.
首先将序号序列化首字节低三位设置为010
在这里插入图片描述
packed方式下,对包含有效数值的repeated字段序列化结构为上述.
分为三个部分,首个部分为序号,第二部分为数据部分尺寸序列化,尺寸序列化采用varint序列化32无符号数值方式进行.至于数据部分序列化,是对字段下数组元素逐个序列化的过程.
不同元素类型,采用方式不同.
c.1. PROTOBUF_C_TYPE_SFIXED32
c.2. PROTOBUF_C_TYPE_FIXED32
c.3. PROTOBUF_C_TYPE_FLOAT

copy_to_little_endian_32(payload_at, array, count);
static void copy_to_little_endian_32(void *out, const void *in, const unsigned n)
{
#if !defined(WORDS_BIGENDIAN)memcpy(out, in, n * 4);
#elseunsigned i;const uint32_t *ini = in;for (i = 0; i < n; i++)fixed32_pack(ini[i], (uint32_t *) out + i);
#endif
}

即对每个元素分别采用前面required部分介绍的方式序列化.
c.4. PROTOBUF_C_TYPE_SFIXED64
c.5. PROTOBUF_C_TYPE_FIXED64
c.6. PROTOBUF_C_TYPE_DOUBLE

copy_to_little_endian_64(payload_at, array, count);
static void copy_to_little_endian_64(void *out, const void *in, const unsigned n)
{
#if !defined(WORDS_BIGENDIAN)memcpy(out, in, n * 8);
#elseunsigned i;const uint64_t *ini = in;for (i = 0; i < n; i++)fixed64_pack(ini[i], (uint64_t *) out + i);
#endif
}

即对每个元素分别采用前面required部分介绍的方式序列化.
c.7. PROTOBUF_C_TYPE_ENUM
c.8. PROTOBUF_C_TYPE_INT32
c.9. PROTOBUF_C_TYPE_SINT32
c.10. PROTOBUF_C_TYPE_SINT64
c.11. PROTOBUF_C_TYPE_UINT32
c.12. PROTOBUF_C_TYPE_INT64
c.13. PROTOBUF_C_TYPE_UINT64
c.14. PROTOBUF_C_TYPE_BOOL
分别对每个元素采用前面required部分介绍的针对此元素类型的方式序列化.

(2). unpacked方式下序列化
在这里插入图片描述
该方式下,采取的方式是对每个数组元素分别按required部分介绍的方式去序列化.每个数组元素的序列化均由包含id信息的tag及数据自身构成.

3.4.未携带修饰符字段序列化
这里我们补充未携带修饰符字段的序列化

static size_t unlabeled_field_pack(const ProtobufCFieldDescriptor *field, const void *member, uint8_t *out)
{if (field_is_zeroish(field, member))return 0;return required_field_pack(field, member, out);
}
static protobuf_c_boolean field_is_zeroish(const ProtobufCFieldDescriptor *field, const void *member)
{protobuf_c_boolean ret = FALSE;switch (field->type) {case PROTOBUF_C_TYPE_BOOL:ret = (0 == *(const protobuf_c_boolean *) member);break;case PROTOBUF_C_TYPE_ENUM:case PROTOBUF_C_TYPE_SINT32:case PROTOBUF_C_TYPE_INT32:case PROTOBUF_C_TYPE_UINT32:case PROTOBUF_C_TYPE_SFIXED32:case PROTOBUF_C_TYPE_FIXED32:ret = (0 == *(const uint32_t *) member);break;case PROTOBUF_C_TYPE_SINT64:case PROTOBUF_C_TYPE_INT64:case PROTOBUF_C_TYPE_UINT64:case PROTOBUF_C_TYPE_SFIXED64:case PROTOBUF_C_TYPE_FIXED64:ret = (0 == *(const uint64_t *) member);break;case PROTOBUF_C_TYPE_FLOAT:ret = (0 == *(const float *) member);break;case PROTOBUF_C_TYPE_DOUBLE:ret = (0 == *(const double *) member);break;case PROTOBUF_C_TYPE_STRING:ret = (NULL == *(const char * const *) member) || ('\0' == **(const char * const *) member);break;case PROTOBUF_C_TYPE_BYTES:case PROTOBUF_C_TYPE_MESSAGE:ret = (NULL == *(const void * const *) member);break;default:ret = TRUE;break;}return ret;
}

即先判断是否属于无需序列化场景.否则,按required字段方式执行序列化.

4.从包含序列化内容的缓存区反向序列化并返回对应的类型实例
反序列化是序列化的逆向过程.理解序列化机制下,按相应策略处理即可.不再详细分析.

5.反向序列化返回的类型实例释放
即释放实例对象.释放过程还需将实例对象所关联的相关动态缓存区也一起同步释放才行.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/758505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何写出干净的 Git Commit

大家好&#xff0c;我是楷鹏。 写一份干净的 Git Commit&#xff0c;不仅赏心悦目&#xff0c;也有诸多好处&#xff0c;比如 为项目或者仓库生成 change log方便在其他一些 Git 工具使用&#xff0c;比如 CI/CD、代码协作和审计平台、发版工具等 这是 AngularJS 仓库的 Git …

短视频矩阵系统技术交付

短视频矩阵系统技术交付&#xff0c;短视频矩阵剪辑矩阵分发系统现在在来开发这个市场单个项目来说&#xff0c;目前基本上已经沉淀3年了&#xff0c;那么我们来就技术短视频矩阵剪辑系统开发来聊聊 短视频矩阵系统经过315大会以后&#xff0c;很多违规的技术开发肯定有筛选到了…

[ C++ ] STL---string类的使用指南

目录 前言&#xff1a; string类简介 string类的常用接口 string类对象的构造函数 string类对象的赋值运算符重载 string类对象的容量操作 string类对象的访问与遍历 [ ] 下标遍历 迭代器遍历 普通迭代器iterator ​编辑 const迭代器const_iterator 反向迭代器rever…

常见的WAFI攻击包括哪些

WIFI攻击是指黑客利用各种手段&#xff0c;对WIFI网络进行非法入侵和攻击&#xff0c;以获取用户的个人信息、网络权限或其他敏感数据。常见的WIFI攻击方式主要包括以下几种&#xff1a;伪造认证页面&#xff1a;黑客可以创建一个伪造的认证页面&#xff0c;当用户尝试连接到WI…

Vue2(三):绑定样式、条件渲染(v-if,v-show)、列表渲染(v-for)、key的原理、列表过滤、列表排序

一、绑定样式 1.绑定class样式 (1)字符串写法 适用于&#xff1a;样式类名不确定&#xff0c;需要动态获取。 <div id"root"><div class"basic" :class"mood" click"changeMood">test</div><!-- class是原本的…

Android Studio实现内容丰富的安卓旅游景点预定

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 1.开发环境 android stuido3.6 jak1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看景点列表 3.查看景点详情 4.景点预定 5.购物车支付结算功能 6…

计算机网络:计算机网络概述

计算机网络&#xff1a;计算机网络概述 因特网概述网络&#xff0c;互连网&#xff0c;因特网因特网发展的三个阶段因特网的标准化工作因特网组成 计算机网络的定义计算机网络的分类按使用者分类按传输介质分类按网络的覆盖范围分类按拓扑结构分类 因特网概述 网络&#xff0c…

红外相机和RGB相机标定:实现两种模态数据融合

1. 前期准备 RGB相机&#xff1a;森云智能SG2-IMX390&#xff0c;1个红外相机&#xff1a;艾睿光电IR-Pilot 640X-32G&#xff0c;1个红外标定板&#xff1a;https://item.taobao.com/item.htm?_ujp3fdd12b99&id644506141871&spma1z09.2.0.0.5f822e8dKrxxYI 2.操作步…

目标检测---IOU计算详细解读(IoU、GIoU、DIoU、CIoU、EIOU、Focal-EIOU、SIOU、WIOU)

常见IoU解读与代码实现 一、✒️IoU&#xff08;Intersection over Union&#xff09;1.1 &#x1f525;IoU原理☀️ 优点⚡️缺点 1.2 &#x1f525;IoU计算1.3 &#x1f4cc;IoU代码实现 二、✒️GIoU&#xff08;Generalized IoU&#xff09;2.1 GIoU原理☀️优点⚡️缺点 2…

网络编程:数据库

一、作业 1> 创建一个工人信息库&#xff0c;包含工号&#xff08;主键&#xff09;、姓名、年龄、薪资。 2> 添加三条工人信息&#xff08;可以完整信息&#xff0c;也可以非完整信息&#xff09; 3> 修改某一个工人的薪资&#xff08;确定的一个&#xff09; 4> …

SAP上线计划Cutover Plan

在SAP项目中&#xff0c;上线计划Cutover Plan(另一说法是切换计划)是指在项目的最后阶段&#xff0c;即从旧系统过渡到新SAP系统的过程中&#xff0c;组织必须执行的一系列活动和步骤的详细计划。这个计划对于确保平稳、有序的系统过渡至关重要。Cutover计划通常涵盖了组织沟通…

【漏洞复现】Progress Kemp LoadMaster 命令注入漏洞(CVE-2024-1212)

0x01 产品简介 Progress Kemp LoadMaster是一款高性能的应用交付控制器&#xff0c;具有可扩展性&#xff0c;支持实体硬件和虚拟机的负载均衡。它提供了当今应用服务所需的各种功能&#xff0c;包括深度用户验证、资安防护&#xff08;如WAF/IPS/DDoS防护&#xff09;以及零信…

2024学习鸿蒙开发,未来发展如何?

一、前言 想要了解一个领域的未来发展如何&#xff0c;可以从如下几点进行&#xff0c;避免盲从&#xff1a; 国家政策落地情况就业市场如何学习 通过上述三点&#xff0c;就能分析出一个行业的趋势。大家可以看到&#xff0c;我上面的总体逻辑就是根据国家政策来分析未来方…

代码随想录day24(2)二叉树:合并二叉树(leetcode617)

题目要求&#xff1a;将两个二叉树合并&#xff0c;要求是将同位置处的两个节点值相加&#xff0c;如果一个为空那就将另一个二叉树的值覆盖。 思路&#xff1a;如果使用迭代法&#xff0c;就是通过层序遍历&#xff0c;通过队列进行判断进行相加。如果使用递归法&#xff0c;…

git基础-获取git仓库

通过本章的学习&#xff0c;应该能够配置和初始化一个仓库&#xff0c;开始和停止跟踪文件&#xff0c;暂存和提交更改。我们还将展示如何设置 Git 来忽略特定的文件和文件模式&#xff0c;如何快速轻松地撤销错误&#xff0c;如何浏览项目的历史记录并查看提交之间的更改&…

酷开科技聚焦大屏端数据研究,构建酷开系统深度挖掘大屏商业价值

中国所有的彩色大屏中&#xff0c;智能电视规模已经过半&#xff0c;OTT平台的数据价值越发引起人们关注。作为OTT行业的头部代表&#xff0c;酷开科技一直聚焦大屏端数据研究&#xff0c;目前已经形成一套基于大屏指数的智慧营销体系&#xff0c;让OTT大屏的数字营销化水平实现…

AI:150-基于深度学习的医学数据挖掘与病症关联发现

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带关键代码,详细讲解供大家学习,希望…

ModuleNotFoundError: No module named ‘torch_geometric‘

1. 解决办法——安装库 pip install torch_geometric -i https://pypi.tuna.tsinghua.edu.cn/simple总结 如果你仍然遇到问题&#xff0c;请确保你的pip或conda是最新版本&#xff0c;并且你正在使用的Python环境是激活的。此外&#xff0c;如果你的PyTorch版本与 torch_geom…

吴恩达机器学习-可选实验室:简单神经网络(Simple Neural Network)

在这个实验室中&#xff0c;我们将使用Tensorflow构建一个小型神经网络 import numpy as np import matplotlib.pyplot as plt plt.style.use(./deeplearning.mplstyle) import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.laye…

win10 配置 oh-my-posh

win10 配置 oh-my-posh 0. 前置1. 安装1.1. 软件1.2. 字体1.3. 激活1.3.1. Git Bash1.3.2. PowerShell 2. 配置2.1. 效果2.2. 说明2.3. 其他2.3.1. 新版PowerShell2.3.2 conda问题 0. 前置 这个东西毕竟是个&#xff0c;命令行美化工具&#xff0c;所以需要先有一个命令行&…