sproto协议对应的二进制数据流格式解析文档


请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

sproto是什么东西本文就不解释了,大家可以从https://github.com/cloudwu/sproto处下载sproto包,看里面的ReadMe文档。本文的内容基本上可以视为ReadMe文档的一个子集。

一个sproto的message近似地对应了一个C++语言的struct,例如有以下的一个C++语言的struct:

struct Person {
    std::string name;
    int age;
    bool marital
    std::vector<Person> children;
}

要描述这样一个C++语言结构的话,可以使用sproto的描述如下:

.Person {
    name 0 : string
    age 1 : integer
    marital 2 : boolean
    children 3 : *Person
}

message中的field是基本数据类型时

要根据这一个叫做Person的message去生成一个具体的消息包时,可以按以下的lua代码定义:

--lua示例-1
person { name = "Alice" ,  age = 13, marital = false }

当使用sproto的解释器去parse这样的一个数据结构Person时,解释器将会依照一定的规则将其序列化为二进制字节流。在解释上面的lua代码到底被序列化成为什么样子的字节流之前,首先看下sproto定义的一些规范和标准。

sproto将会把一个message编码成3部分,分别是header部field部data部

header部可以认为是消息包的消息头。它是一个16位的整数,并且是以litte endian的形式存在于内存中。它对应存储了某个消息中field的个数。而消息中的field就是指里面的数据项,以Person为例,field就是name,age,matrital,children这四项。所以header的数值为4。

对应于lua示例-1中所定义person table,内存中对应的字节如下表所示:

内存中字节值 所处部分 描述
03 00 header 本message中的有3个field,所以header中的值为3
00 00 field field序号为0的field的值,因为是字符串,所以field的具体值不存放在field部而是在data部,故而此处的field值为0
1C 00 field field序号为1的field值,因为是整数,所以具体值存在于field部。这里的field值为1C,即十进制的28,其对应的数据段实际值(field值 / 2 - 1), 得到的数据段实际值实际值为定义的13。
02 00 field field序号为1的field值,因为是布尔值,所以具体值存在于field部。这里的field值为02,即十进制的2,其对应的数据段实际值(field值 / 2 - 1), 得到的数据段实际值实际值为定义的0,即是false
05 00 00 00 data 对应于field序号为0的那个field的内容的字节长度,sproto规定field是一个数组的话或者字符串的话,内容要放在data部data部中,头四个字节存放了这个field内容的字节长度,因为字符串“Alice”的utf8编码长度为5个字节,所以这里的值为5
41 6C 69 63 65 data 对应于field序号为0的那个field的内容。即字符串“Alice”的utf8编码值

message中有field是数组时

lua示例-1中,没有使用Person message所定义的children字段,现在看看使用了children字段的lua table,其数据在内存中的布局是如何

-- lua示例-2
person {
    name = "Bob",
    age = 40,
    --[[ marital = true, 默认跳过婚否的项--]]
    children = {
        { name = "Alice" ,  age = 13 },
        { name = "Carol" ,  age = 5 },
    }
}

对应于lua示例-2中所定义person table,内存中对应的字节如下表所示:

内存中字节值 所处部分 描述
04 00 header 虽然在lua table中只是显式地定义了3项field,但是因为最后一个field:children是有显式地定义了,故而这个messsge里面field的个数依然算作四项,所以header部分的值为4
00 00 field field序号为0的field的值,因为是字符串,所以field的具体值不存放在field部而是在data部,故而此处的field值为0
52 00 field field序号为1的field值,因为是整数,所以具体值存在于field部。这里的field值为52,即十进制的82,其对应的数据段实际值(field值 / 2 - 1), 得到的数据段实际值实际值为定义的40。
01 00 field (skip id = 2)
00 00 field field序号为3的field的值,因为是数组,所以field的具体值不存放在field部而是在data部,故而此处的field值为0
03 00 00 00 data 对应于field序号为0的那个field的内容的字节长度,sproto规定field是一个数组的话或者字符串的话,内容要放在data部data部中,头四个字节存放了这个field内容的字节长度,因为字符串“Bob”的utf8编码长度为3个字节,所以这里的值为3
42 6F 62 data 对应于field序号为0的那个field的内容。即字符串“Bob”的utf8编码值
26 00 00 00 data 对应于field序号为3的那个field,即children的内容字节长度,其值为0x26,即十进制的38个字节
0F 00 00 00 data children的第一个元素的字节大小,这是sproto的要求,在描述数组中的每一个元素之前,必须要首先使用4个字节去记录下该元素的字节数。children[0]为15个元素
02 00 header children[0]只启用了age和name两个选项,所以header部分的值为2
00 00 field field序号为0的field的值,因为是字符串,所以field的具体值不存放在field部而是在data部,故而此处的field值为0
1C 00 field field序号为1的field值,因为是整数,所以具体值存在于field部。这里的field值为0x1C,即十进制的28,其对应的数据段实际值(field值 / 2 - 1), 得到的数据段实际值实际值为定义的13
05 00 00 00 data 对应于field序号为0的那个field的内容的字节长度,sproto规定field是一个数组的话或者字符串的话,内容要放在data部data部中,头四个字节存放了这个field内容的字节长度,因为字符串“Alice”的utf8编码长度为5个字节,所以这里的值为5
41 6C 69 63 65 data 对应于field序号为0的那个field的内容。即字符串“Alice”的utf8编码值
0F 00 00 00 data children的第2个元素的字节大小,这是sproto的要求,在描述数组中的每一个元素之前,必须要首先使用4个字节去记录下该元素的字节数。children[1]为15个元素。
02 00 header children[1]只启用了age和name两个选项,所以header部分的值为2
00 00 field field序号为0的field的值,因为是字符串,所以field的具体值不存放在field部而是在data部,故而此处的field值为0
0C 00 field field序号为1的field值,因为是整数,所以具体值存在于field部。这里的field值为0x0C,即十进制的12,其对应的数据段实际值(field值 / 2 - 1), 得到的数据段实际值实际值为定义的5
05 00 00 00 data 对应于field序号为0的那个field的内容的字节长度,sproto规定field是一个数组的话或者字符串的话,内容要放在data部data部中,头四个字节存放了这个field内容的字节长度,因为字符串“Carol”的utf8编码长度为5个字节,所以这里的值为5
41 6C 69 63 65 data 对应于field序号为0的那个field的内容。即字符串“Carol”的utf8编码值