调试解析直播弹幕消息protobuf内容,一步一步教你debug查看PushFrame和Response解码在哪里

652 阅读3分钟

我们知道直播间的弹幕消息是通过websocket传输的,而且传输的并不是明文数据,而是protobuf消息,至于为什么使用这个protobuf消息,因为它是二进制传输,更快更稳,相对于直播这种实时性比较高的要求,使用这种消息传输是非常合适的。

websocket连接

我们先要在web直播端看一下websocket的连接是在哪里建立的,至于怎么看这个websocket在哪里建立的,可以监听发送的ws请求,找到这个发送请求的代码位置:

监听消息

找到onMessage这个方法,这里面就是给这个socket实例添加了 this.socket.addEventListener("message", e) 方法,然后看一下这个e就是目标监听函数,这个函数在哪里呢?继续debug往下找:

再看看这个_receiveMessage函数里面是啥:

这里面还嵌套了一层es函数,这个es其实就是一个promise:

_receiveMessage里面就是处理收到消息的逻辑了,比较复杂,我们可以单独把它拿出来,然后添加上备注看一下大概都是什么意思。把代码拿出来,我们单独看一下里面的逻辑是啥:

会到debug状态,看一下这个e此时怎么像一个消息呢?没错,它就是一个消息:

再来看看t是啥?这怎么那么像弹幕或者聊天或者礼物或者观众的消息呢?是的,它就是:

每一个消息内容都有一个payload,里面就是真正的消息:

这里的ack消息里面就是需要使用PushFrame这个消息,里面添加payload_type + payload+LogID编码来的。

 

解析消息

上面的消息和payload内容都是二进制,怎么显示出来二进制的呢?

查看一下调用栈,发现这些消息都是送s里面导出来的,那这个s是从哪里来的?

s是这个 transport.decode(new Uint8Array(e.data)) 解析出来的:

那这个transport是啥,怎么解析的呢?找到了:

我们把代码折叠一下:这里就是创建了一个class e,其实这个e就是transport的类

看代码:

var g = f;

.....

而这个f就是下面的代码,也就是我截图的那个class e:
f = class e {
            constructor() {
                this.cachedType = {},
                this.loading = null,
                this.loadSchema = ()=>{
                    "undefined" != typeof window && window.requestIdleCallback(()=>{
                        this._loadSchema()
                    }
                    )
                }
                ,
                this._loadSchema = ()=>(this.loading || (this.loading = (0,
                n.C)(this, null, function*() {
                    if (u.roots.transport) {
                        this.root = u.roots.transport,
                        this.loading = Promise.resolve();
                        return
                    }
                    yield(0,
                    o.y)(),
                    yield r.e(2986).then(r.bind(r, 69949)),
                    this.root = u.roots.transport,
                    this.loading = Promise.resolve()
                })),
                this.loading)
            }
            static get instance() {
                return e.__instance ? e.__instance : e.__instance = new e
            }
            static addRelation(t, r) {
                e.relation[t] = r,
                e.relation[r] = t
            }
            static setRelation(t) {
                e.relation = (0,
                n.i)((0,
                n.i)({}, e.relation), null != t ? t : {})
            }
            getType(t) {
                let r = t.replace(a.nl, "")
                  , i = this.cachedType[r];
                if (i)
                    return i;
                try {
                    let i = [e.relation[t], e.relation[r], r, t].filter(e=>e)
                      , n = i.map(t=>e.typeHintPrefix.map(e=>`${e}.${t}`)).reduce((e,t)=>e.concat(t)).concat(i);
                    d("search types", n);
                    let o = n.reduce((e,t)=>e && "function" == typeof e ? e : t.split(".").reduce((e,t)=>null == e ? void 0 : e[t], this.root), void 0);
                    if ("function" != typeof o)
                        throw Error("cannot find type");
                    return o
                } catch (e) {
                    return d(`no current schema[${String(r)}]`),
                    null
                }
            }
            // 这里就是transport的decode代码
            decode(e, t) {
                return (0,
                n.C)(this, null, function*() {
                    var r, i, n, o, a, s, l, c, u;
                    if (yield this._loadSchema(),
                    t)
                        return this._decode(e, t);
                    let[p,h] = yield this._decodeFrameOrResponse(e)
                      , d = null != (o = null != (n = null == (i = null == (r = null == h ? void 0 : h.headers) ? void 0 : r.find(e=>"im-cursor" === e.key)) ? void 0 : i.value) ? n : p.cursor) ? o : ""
                      , m = null != (c = null != (l = null == (s = null == (a = null == h ? void 0 : h.headers) ? void 0 : a.find(e=>"im-internal_ext" === e.key)) ? void 0 : s.value) ? l : p.internal_ext) ? c : "";
                    return {
                        response: p,
                        frame: h,
                        needAck: null != (u = p.need_ack) && u,
                        cursor: d,
                        internalExt: m
                    }
                })
            }
            encode(e, t) {
                return (0,
                n.C)(this, null, function*() {
                    return yield this._loadSchema(),
                    this._encode(e, t)
                })
            }
            ack(e, t) {
                return (0,
                n.C)(this, null, function*() {
                    var r, i, n, o;
                    let l = null != (o = null != (n = null == (i = null == (r = e.headers) ? void 0 : r.find(e=>"im-internal_ext" === e.key)) ? void 0 : i.value) ? n : t.internal_ext) ? o : "";
                    return yield this.encode({
                        payload_type: a.AG.Ack,
                        payload: s(l),
                        LogID: e.LogID
                    }, "PushFrame")
                })
            }
            ping() {
                return this.encode({
                    payload_type: a.AG.Hb
                }, "PushFrame")
            }
            _decodeFrameOrResponse(e) {
                return (0,
                n.C)(this, null, function*() {
                    try {
                        let t = this._decode(e, "PushFrame")
                          , r = yield this._extractResponse(t);
                        return [this._decode(r, "Response"), t]
                    } catch (t) {
                        return [this._decode(e, "Response")]
                    }
                })
            }
            _extractResponse(t) {
                return (0,
                n.C)(this, null, function*() {
                    var r;
                    return (null == (r = t.headers) ? void 0 : r.some(e=>"compress_type" === e.key && "gzip" === e.value)) ? yield e.unGzip(t.payload) : t.payload
                })
            }
            _decode(e, t) {
                let r = this.getType(t);
                if (!r)
                    return;
                let i = r.decode(e);
                return d("decoded success", t, i),
                m("decoded success", e),
                i
            }
            _encode(e, t) {
                let r = this.getType(t);
                if (!r)
                    return;
                let i = r.encode(e).finish();
                return d("encoded success", t, e),
                m("encoded success", i),
                i
            }
        }

那这个decode里面怎么解析数据的?看代码,找到_decode和_decodeFrameOrResponse这两个函数的内容,看到了什么?原来解码的就在这里啊:

到这里我觉得你应该就知道怎么解析了吧,下课