背景

个别玩家反馈,某个活动打开时,消费列表显示错误,经前端日志排查发现是因为该玩家的消费信息解析失败了

我们的 protobuf 的简易配置

syntax = "proto3";

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
}

message MsgPayInfo {
    repeated Pay pay_list = 1;
    int32 flag=2;
}

经过就线上数据的排除测试,发现错误数据

  • 当tc的值是 127的浮点数时会报错,protobuf解析失败,例如:
lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]

开始猜想

  • tc是 int32 类型,赋值时使用浮点数会出错
  • 尝试发送几个数据 可以发送浮点数,并且不会报错
lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]

lua脚本层发送数据 
    {pay_list = {{tc=126.05}}}
protobuf编译后 
    uint8_t* = [10,3,176,1,126]

lua脚本层发送数据 
    {pay_list = {{tc=127}}}
protobuf编译后 
    uint8_t* = [10,3,176,1,127]

观察几个数据我们得出一些结论:

  1. 当发送tc<=127时,第二个数字是3
    • [10,3,176,1,126]
    • [10,3,176,1,127]
  2. 当发送tc>=128时,第二个数字是4
    • [10,4,176,1,128,1]
  3. 与之对应的是发送tc=128时,uint8数组长度多了1个数据

  4. 猜想第二个数字的3、4可能代表着后续数据长度

后续观察出错的数据

lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]
  • 这个数据的第二个数字是4,而他的后续数据长度只有3
  • !!!可能是因为这样所以数据长度对不上,导致protobuf解析消息失败了!!!

查看官网的protobuf协议文档验证我们的猜想

官网文档的白话理解以报错数据为例子

lua脚本层发送数据 
    {pay_list = {{tc=127.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,127]
  • 第一个数据 10, 我们可以根据公式 (field_number << 3) | wire_type 将解析出字段编号,和后续读取数据的类型
// 10的二进制对应00001010
10 = 0b00001010
可以解析出
field_number = 0b00001 = 1
wire_type = 0b010 = 2

wire_type是什么意思呢?继续看文档

ID Name Used For
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
1 I64 fixed64, sfixed64, double
2 LEN string, bytes, embedded messages, packed repeated fields
3 SGROUP group start (deprecated)
4 EGROUP group end (deprecated)
5 I32 fixed32, sfixed32, float
  • 我们回到我们上面错误的例子
message MsgPayInfo {
    repeated Pay pay_list = 1;
    int32 flag=2;
}

// 10的二进制对应00001010
10 = 0b00001010
可以解析出
field_number = 0b00001 = 1 代表proto中编号为1的字段,既 pay_list
wire_type = 0b010 = 2

  • wire_type是2代表的,接下来是一个长度值

!!!我们的猜想是对的,第二位代表着数据长度值,而我们收到的会报错的数据,长度值不对!!!

我们继续看为什么当tc=128.05的时候可以发送成功

lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]
  • 可以看出,第二位是4,而后续确实有4个数据,所以能解析成功

那么[176,1,128,1]又是如何解析出

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
}

176 = 0b10110000
field_number = 0b10110 = 22   代表proto中编号为22的字段,既 tc  !!!这是错的,凑巧对上了,后续会说明!!!
wire_type = 0b000 = 0

  • wire_type是0代表的,接下来是一个VARINT

开始有了新疑问?只有5位代表编号,也就31,线上使用的字段不止31

message Pay {
    '''
    string role_id = 2;
    '''
    int32 tc=22;
    '''
    int32 tc1=1;
    int32 tc15=15;
    int32 tc16=16;
    int32 tc30=30;
    int32 tc31=31;
    int32 tc32=32;
}

尝试发送几个数据

lua脚本层发送数据 
    {pay_list = {{tc1=1}}}
protobuf编译后 
    uint8_t* = [10,2,8,1]

lua脚本层发送数据 
    {pay_list = {{tc15=15}}}
protobuf编译后 
    uint8_t* = [10,2,120,15]

lua脚本层发送数据 
    {pay_list = {{tc16=16}}}
protobuf编译后 
    uint8_t* = [10,3,128,1,16]

lua脚本层发送数据 
    {pay_list = {{tc30=30}}}
protobuf编译后 
    uint8_t* = [10,3,240,1,30]

lua脚本层发送数据 
    {pay_list = {{tc31=31}}}
protobuf编译后 
    uint8_t* = [10,3,248,1,31]

lua脚本层发送数据 
    {pay_list = {{tc32=32}}}
protobuf编译后 
    uint8_t* = [10,3,128,2,32]


!!!发现几个结论!!!

  1. 当字段编号小于<=15时候 数据[120,15]长度是2
  2. 当字段编号在 16-31的时候,数据[248,1,31]长度是3
  3. 当字段编号在 大于31的时候, 数据[128,2,32]第二位会发生变化

我们文档查看发生了什么

回到我们的例子[176,1,128,1]

lua脚本层发送数据 
    {pay_list = {{tc=128.05}}}
protobuf编译后 
    uint8_t* = [10,4,176,1,128,1]

[176,1]用来解析字段长度

  • 数字:176
    • field_number = 0b10110
      • 根据varints白话第一点,还有后续数据
      • 保留其余部分 0b0110
  • 数字:1
    • 因为176中数据未取完,继续读取
    • 1 = 0b00000001
  • 根据 varints 白话理解 第三点 以小端序合并数据
    • 00000001 0110 = 000000010110 = 22
    • 得到字段编号为22

[128,1] 是我们的数据 128

  • 数字:128
    • 0b128 = 10000000
    • 根据varints白话第一点,还有后续数据
    • 保留其余部分 0b0000000
  • 数字:1
    • 因为128中数据未取完,继续读取
    • 1 = 0b00000001
  • 根据 varints 白话理解 第三点 以小端序合并数据
  • 00000001 0000000 = 000000010000000 = 128
  • 得到数据128

解决

  • 将数据 * 100 取整后在发送给前端

额外的收获

  • 当我们字段编号>=16的时候,会导致数据多使用一个 uint8的长度

发表评论