注意 Protobuf 中的空字符串

今天在用 Python Protobuf 序列化数据时碰到个坑,折腾了几个小时才解决。原因是 proto3 和 proto2 对空字符串的处理机制有区别。

原本是用的 proto3,.proto 文件内容如下,其中 sig 字段可能为空。

syntax = "proto3";
package sendHandshakePack;

message Message {
    uint32 msgid = 1;
    uint64 sn = 2;
    string sender = 3;
    Request req = 6;
    string sender_type = 12;

    message Request {
        InitLoginReq init_login_req = 9;

        message InitLoginReq {
            string client_ram = 1;
            string sig = 2;
        }
    }

}

Python 赋值再序列化的代码:

p = handshakepack_pb2.Message()

p.msgid = 100009
p.sn = 6222108933
p.sender = '9991593214588246974167'
p.sender_type = 'jid'
p.req.init_login_req.client_ram = 'EXKf8I08PT'
p.req.init_login_req.sig = ''

data = p.SerializeToString()
print(data)
print(len(data))

打印序列化后的字节流:b'\x08\xa9\x8d\x06\x10\x85\xb2\xf7\x96\x17\x1a\x1699915932145882469741672\x0eJ\x0c\n\nEXKf8I08PTb\x03jid'。其长度为:55

这段字节流发给服务器,始终得不到成功的响应。通过抓取之前请求成功的数据,看到其发送的字节流是这样的:b'\x08\xa9\x8d\x06\x10\x85\xb2\xf7\x96\x17\x1a\x1699915932145882469741672\x10J\x0e\n\nEXKf8I08PT\x12\x00b\x03jid',长度为:57

对比上面两段字节流,可以确定的是与 sig 的空值有关:修改 sig 的值则会导致序列化的结果不同。只怪自己学艺不精,始终找不到原因。

最后在 GitHub 上找到类似问题,引用某大神的回复:

Q:Protobuf 传递空字符时服务器无法解释报文?

A:由于 proto3 中处理空字符串的机制与 proto2 不同,proto2 中空字符串会写入字段标头,proto3 中会跳过整个字段,导致生成的字节流无法在 proto2 中解码。

根据上面的说法,把.proto 文件改成 proto2 格式,序列化后即为正确的字节流了。

syntax = "proto2";
package sendHandshakePack;

message Message {
    required uint32 msgid = 1;
    required uint64 sn = 2;
    optional string sender = 3;
    optional Request req = 6;
    optional string sender_type = 12;

    message Request {
        optional InitLoginReq init_login_req = 9;

        message InitLoginReq {
            required string client_ram = 1;
            optional string sig = 2;
        }
    }

}

引用知乎上的某篇文章:

  • 在 Protobuf 2 中,消息的字段可以加 required 和 optional 修饰符,也支持 default 修饰符指定默认值。默认配置下,一个 optional 字段如果没有设置,或者显式设置成了默认值,在序列化成二进制格式时,这个字段会被去掉,导致反序列化后,无法区分是当初没有设置还是设置成了默认值但序列化时被去掉了,即使 Protobuf 2 对于 原始数据类型  字段都有 hasXxx () 方法,在反序列化后,对于这个 “缺失” 字段,hasXxx () 总是 false—— 失去了其判定意义。
  • 在 Protobuf 3 中,更进一步,直接去掉了 required 和 optional 修饰符,所有字段都是 optional 的, 而且对于 原始数据类型 字段,压根不提供 hasXxx () 方法。

顺便记录一个小问题:

Python 使用 protobuf 时, add () 方法只有 proto2 的 repeated 字段才有,如果是 required、optional 等字段则会出现 AttributeError: 'xxx' object has no attribute 'add' 的错误。

参考资料:

Protobuf 传递空字符时服务器无法解释报文

区分 Protobuf 中缺失值和默认值

object has no attribute 'add' on python's protobuf

ProtoBuf.js 使用技巧

» 链接地址:https://wbt5.com/protobuf-empty.html »英雄不问来路,转载请注明出处。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注