我们知道直播间的弹幕消息是通过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这两个函数的内容,看到了什么?原来解码的就在这里啊:
到这里我觉得你应该就知道怎么解析了吧,下课