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.h
,test_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
序列化尺寸固定为:4
.
b.10. PROTOBUF_C_TYPE_SFIXED64
b.11. PROTOBUF_C_TYPE_FIXED64
序列化尺寸固定为:8
.
b.12. PROTOBUF_C_TYPE_BOOL
序列化尺寸固定为:1
.
b.13. PROTOBUF_C_TYPE_FLOAT
序列化尺寸固定为:4
.
b.14. PROTOBUF_C_TYPE_DOUBLE
序列化尺寸固定为:8
.
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_MESSAGE
,PROTOBUF_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
每个元素固定尺寸为:4
.
a.11. PROTOBUF_C_TYPE_SFIXED64
a.12. PROTOBUF_C_TYPE_FIXED64
a.13. PROTOBUF_C_TYPE_DOUBLE
每个元素固定尺寸为:8
.
a.14. PROTOBUF_C_TYPE_BOOL
每个元素固定尺寸为:1
.
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);
else
return 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 0101 (9a 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);
}
其序列化过程是先通过zigzag32
将int32_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
个比特位构成一个字节,直到处理完毕所有有效比特位.且设置除最后一个字节外的其他字节最高位为1
的Varints
编码过程.采用上述过程处理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);
#else
uint8_t *buf = out;
buf[0] = value;// 低8比特位
buf[1] = value >> 8;// 次低8比特位
buf[2] = value >> 16;// 次次低8比特位
buf[3] = value >> 24;// 最高8比特位
#endif
return 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);
#else
fixed32_pack(value, out);
fixed32_pack(value >> 32, ((char *) out) + 4);
#endif
return 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);
}
当字段类型为MESSAGE
或STRING
时,若字段数值部分为空指针或为字段默认值.不占用序列化空间.
其他类型字段,当其关联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);
#else
unsigned 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);
#else
unsigned 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.反向序列化返回的类型实例释放
即释放实例对象.释放过程还需将实例对象所关联的相关动态缓存区也一起同步释放才行.