Python [解码问题请教] 基于 protobuf 的 websockets 接收服务器返回数据后,

陈随想 · 2020年12月26日 · 最后由 陈随想 回复于 2020年12月29日 · 830 次阅读

问题

写了个 websockets 连接服务器,用的是 protobuf 协议,序列化成功后发送服务器,成功收到返回(这里以登录协议来说明),并且确认了日志和游戏另一个客户端成功踢下线。然后收到服务器返回之后用 ParseFromString 去反序列化,也是成功拿到了返回的数据。但是返回数据中的 data 字段其实是一个嵌套的 protobuf,这里尝试多次实在不知如何解码,请论坛各位大佬指点迷津。

部分代码如下:

async def main_logic():
    async with websockets.connect(_server_ws_url) as ws:
        await ws.send(loginmsg.SerializeToString())
        recv_text = await ws.recv()
        logininfo.ParseFromString(recv_text)
        print(logininfo)
        print(logininfo.result, logininfo.messageId)
//服务端发给客户端的命令数据
message ServerCmdData {
    required sint32 messageId = 1;
    required sint32 clientIndex = 2;
    required sint32 serverIndex = 3;
    optional bytes data = 4;//命令主数据
    required bool result = 5;
}

拿到的返回数据,反序列化之后结果如下

messageId: 1001
clientIndex: 0
serverIndex: 1
data: "\n<18c04dfffe136cb9-000017cc-0000000f-2a58a19679d30b77-2e650bea\022V\010\226\254\366\300\235\003\022\006poke22\030\027 \327\017(\352\0140\214\211\321\322\0038\324>@\000H\241\302\036P\000X\000`\000h\017p\244Nx\000\200\001\345\304\023\210\001\000\220\001\003\230\001\361\274\014\240\001\336\332\220\377\005\250\001\000\260\001\000\270\001\000\300\001\000"
result: true

客户端发送大概是这样的:第一层 message 数据序列化,作为客户端 message 的 data 数据,再整个 message 序列化发送。所以我以为服务器也是这样,登录返回相关的 message 序列化后作为 message ServerCmdData 的 data(byes 类型) 主数据,再整个 message ServerCmdData 序列化后发送给客户端。但是我不知道怎么解码接收到的 data。

共收到 7 条回复 时间 点赞

@jiazurongyu 游戏行业的老大哥,不知道能否指点一下

额,看起来你这个是没有对 ws 协议做一个包头解析,导致你拿到的数据长度不对,所以反序列化出来的数据不正确

账号更换 回复

感谢老哥指点方向。现在反正代码能用,后面我再往这个方向研究一下,顺带补一下基础知识。

是不是可以考虑给 data 写一个 message,然后将可能有返回值的都预设好,这样反序列化出来应该不会有问题。

陈随想 回复

不好意思,刚看到
首先:websocket 正常情况下是没有做分包,所以不用从包头取包体对象。
客户端序列化数据-->服务端,但不代表服务器发回给客户端的一定是同样序列化方式,具体看业务。
websocket 收包按游戏来说需要设置为二进制的形式,默认是 text。
代码不是很全,logininfo 应该是 一个 pb 对象函数返回,然后 logininfo.ParseFromString(xxxx) 才能进行反序列化。
messageId 是协议号。另外 websocket+pb 一般会做一些签名验证的方式来确保包安全性,所以可以问问是否在发包前和后需要对这块进行签名和解签名。

最后,让 pb 对象可视化一点,可以用 pbjson.py。网上开源的。

陈子昂 回复

多谢指点。loginifo 的确是 message ServerCmdData 的 pd 返回对象。loginmsg 则是 message ClientCmdData 的 pd 返回对象。

    def playerlogin(self):
        logininfo = CmdPlayer_pb2.CmdPlayerLoginReqMsg()
        logininfo.serverId = self.serverid
        logininfo.accountId = self.accountid
        # logininfo.sign = eval(ms4)["data"]["token"]
        logininfo.sign = self.token
        logininfo.loginCheckTime = int(round(time.time() * 1000))
        return logininfo

    def clientmsg(self, messageid, msg=None):
        cmsg = Cmd_043e09dcbe_pb2.ClientCmdData()
        cmsg.messageId = messageid
        cmsg.clientIndex = 0
        if msg:
            cmsg.data = msg
        return cmsg
}
message ClientCmdData {
    required sint32 messageId = 1;//命令ID
    required sint32 clientIndex = 2;
    optional bytes data = 3;//命令主数据
}

msg = protoinfo.playerlogin()
loginmsg = protoinfo.clientmsg(1001, msg.SerializeToString())

我自己尝试的发送,就是 logininfo.SerializeToString() 作为 ClientCmdData 的 pd 对象的 data 值,再构造整个 ClientCmdData 序列化之后发送的。游戏可太难受了,最近加班加到死~~赶明儿继续研究吧,代码现在能用,能录制协议内容发送,目的是达到了的。
多谢抽空解答。

老枸杞 回复

多谢答复~~赶明儿结合各位老哥的智慧,再试试

陈随想 关闭了讨论 02月03日 18:19
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册