消息收发与服务器或者其他客户端进行消息通讯时通常会基于业务约定协议来封装解析消息 。由于都是异步行为 , 需要有唯一标识来处理消息回调 。这里用自增seq来标记 。
发送消息【互动直播中的前端技术——即时通讯】class Ws extends EventEmitter {seq = 0;cmdTasksMap = {};// ...sendCmd(cmd, params) {return new Promise((resolve, reject) => {this.cmdTasksMap[this.seq] = {resolve,reject};const data = https://www.isolves.com/it/wl/js/2020-06-16/genPacket(cmd, params, this.seq++);this.link.send({ data });});}}
接受消息class Ws extends EventEmitter {// ...onMessage(packet) {const data = https://www.isolves.com/it/wl/js/2020-06-16/parsePacket(packet);if (data.seq) {const cmdTask = this.cmdTasksMap[data.seq];if (cmdTask) {if (data.body.code === 200) {cmdTask.resolve(data.body);} else {cmdTask.reject(data.body);}delete this.cmdTasksMap[data.seq];}}}}
生产环境中优化上文只介绍了基础功能的简单封装 , 在生产环境中使用 , 还需要对考虑很多因素 , 尤其是在互动直播场景中 , 礼物展示 , 麦序(进行语音通话互动的顺序) , 聊天 , 群聊等都强依赖长链接的稳定性 , 下面就介绍一些兜底与优化措施 。
连接保持为了稳定建立长链接与保持长链接 。采用了以下几个手段:
- 超时处理
- 心跳包
- 重连退避机制
class Ws extends EventEmitter {// ...sendCmd(cmd, params) {return new Promise((resolve, reject) => {this.cmdTasksMap[this.seq] = {resolve,reject};// 加个定时器this.timeMap[this.seq] = setTimeout(() => {const err = new newTimeoutError(this.seq);reject({ ...err });}, CMDTIMEOUT);const data = https://www.isolves.com/it/wl/js/2020-06-16/genPacket(cmd, params, this.seq++);this.link.send({ data });});}onMessage(packet) {const data = parsePacket(packet);if (data.seq) {const cmdTask = this.cmdTasksMap[data.seq];if (cmdTask) {clearTimeout(this.timeMap[this.seq]);delete this.timeMap[this.seq];if (data.body.code === 200) {cmdTask.resolve(data.body);} else {cmdTask.reject(data.body);}delete this.cmdTasksMap[data.seq];}}}}
心跳包心跳包: 心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字 , 按照一定的时间间隔发送 , 类似于心跳 , 所以叫做心跳包 。心跳包是检查长链接存活的关键手段 , 在web端我们通过心跳包是否超时来判断 。TCP中已有 keepalive选项 , 为什么要在应用层加入心跳包机制?
- tcp keepalive检查连接是否存活
- 应用keepalive检测应用是否正常可响应
如果服务端只认心跳包作为连接存在判断 , 那就在连接建立后定时发心跳就行 。如果以收到包为判断存活 , 那就在每次收到消息重置并起个定时器发送心跳包 。
class Ws extends EventEmitter {// ...onMessage(packet) {const data = https://www.isolves.com/it/wl/js/2020-06-16/parsePacket(packet);if (data.seq) {const cmdTask = this.cmdTasksMap[data.seq];if (cmdTask) {clearTimeout(this.timeMap[this.seq]);if (data.body.code === 200) {cmdTask.resolve(data.body);} else {cmdTask.reject(data.body);}delete this.cmdTasksMap[data.seq];}}this.startHeartBeat();}startHeartBeat() {if (this.heartBeatTimer) {clearTimeout(this.heartBeatTimer);this.heartBeatTimer = null;}this.heartBeatTimer = setTimeout(() => {// 在sendCmd中指定heartbeat类型seq为0 , 让业务包连续编号this.sendCmd('heartbeat').then(() => {// 发送成功了就不管}).catch((e) => {this.heartBeatError(e);});}, HEARTBEATINTERVAL);}}
重连退避机制连不上了 , 重连 , 还连不上 , 重连 , 又连不上 , 重连 。重连是一个保活的手段 , 但总不能一直重连吧 , 因此我们要用合理策去重连 。通常服务端会提供lbs(Location Based Services , LBS)接口 , 来提供最优节点 , 我们端上要做便是缓存这些地址并设定端上的重连退避机制 。按级别次数通常会做以下处理 。
推荐阅读
- 茶叶在旅行中的作用,白茶的保健功效介绍
- 刘涛|41岁的刘涛直播倒立,灵活度不输年轻人,娱乐圈明星健身有多厉害
- 敦煌月牙泉其中的水,辨证茶疗与疾病的关系
- 个人如何搭建Rtmp服务结合uni-app开发直播APP
- 人的身材在一天中的什么时候最高?
- 一款强大的本地文件内容搜索软件,可搜索文件中的文字
- 图解 Go 微服务中的熔断器和重试
- 血缘关系在中国文化中的作用
- Netty 中的内存分配浅析
- 茶艺中的弄茶手法,中国传统茶艺介绍