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