uniapp的websocket无法监听到onSocketClose和onSocketError
项目框架使用的前端uniapp+后端laravel+workerman websocket写的,主要功能是聊天室系统。
但是在开发完成实际体验过程中发现了一个有趣的现象。
websocket老是无缘无故的断开,且没有触发 onSocketClose和onSocketError回调。
查找原因吧,结果发现
在开发过程中基本上是马上写了就马上就测试了,没有长时间的链接测试。客户在使用过程中,一天都把聊天室挂上的,可能早上挂上了,一上午都没有发送消息,然后中午来发消息发现链接被断开了。。
猜测一下,有可能是防火墙或者啥啥啥的玩意把我们的聊天室链接kill掉了或者网络波动等一众原因吧,(反正就是非正常断开链接)
我们之前的用了一个值来存储websocket是否处于链接状态,当发送断开回调或者错误回调的使用就去尝试重新连接,但是没有触发回调也就没有去尝试重新连接了。
上解决方案吧,利用websocket的心跳来做链接存活检查
讲解一下思路:
在我们连接到websocket服务后需要设置一个定时器来向服务器发送心跳数据包(让服务器知道我还活着)。
一般来说服务器也会发送一个收到心跳数据包的回复,我这里定义为ping和pong。
客户端ping服务端,服务器pong客户端。
再每次收到服务器的pong数据包的时候我就记录一下收到该心跳回复数据时候的时间戳。
有了这个时间戳我们就知道当前的websocket服务链接是否被非正常关闭了。
例如我们的心跳时间为20秒,我们设置一个25秒的定时器,那5秒留给网络波动的冗余,用定时器来检测如果时间戳已经20秒没更新了那么就说明我们的websocket断开了需要重连。
我们这里直接用用定时器继续调用
this.socket.open()
方法就行了,因为在open方法中也已经写上了,如果时间戳更新了就不需要重连,提示了
console.log("socket已经开启");
但是如果时间戳没被正确更新,那么重连就是了,并且重连时间就是我们这个定时器的时间周期
当然还有另外一种方式,既然有心跳,那么在发送心跳的地方
uni.sendSocketMessage({ data: {type:"ping"}, success: (s) => {}, fail: (f) => { onsole.log(f); } })
fail失败回调里面也行,也可以判断,当时这种方式有一个问题就是,如果出于其他原因导致的发送失败会造成我们误判链接被关闭了(实际上链接没被关闭,只是发送失败了)
所以多种方法结合起来更好
下面上代码吧
import request from './request.js'; export default { list: [], isLogin: false, url: 'wss://chat.nnjingfang.com/wss', ST: false, ping: '', t1: '', isOpen: false, pongTime: 0, open() { let time = Math.round(new Date() / 1000) console.log(time - this.pongTime); if ((time - this.pongTime) < 60) { //链接是开启的 console.log("socket已经开启"); return; } this.ST = uni.connectSocket({ url: this.url, complete: (e) => {}, fail: (f) => { console.log(f); }, success: (s) => { //this.isLogin = true } }); if (!this.ST) return //防止错误open this.ST.onOpen(() => { this.isLogin = true this.pongTime = Math.round(new Date() / 1000) console.log('连接成功'); this.t1 = window.setInterval(() => { let msg = JSON.stringify({ 'type': 'ping', 'data': { uid: request.uid, } }) uni.sendSocketMessage({ data: msg, success: (s) => {} }) }, 20000); }) this.ST.onMessage((res) => { let ret = JSON.parse(res.data) if (ret) { if (ret.code == 200) { if (ret.type == "pong") { this.pongTime = Math.round(new Date() / 1000) } if (ret.type == "init") { let msg = JSON.stringify({ 'type': 'bind', 'data': { uid: request.uid, token: request.token, datatype: request.datatype } }) uni.sendSocketMessage({ data: msg }) } if (ret.type == "user" || ret.type == "system") { if (this.list[ret.gid]) { console.log(ret); this.list[ret.gid].list.push(ret) if (ret.msg.userinfo.uid !== request.uid) { this.list[ret.gid].config.number += 1 } uni.$emit('update', ret) } } } else { uni.showToast({ title: ret.msg, icon: 'loading' }) } } }) uni.onSocketOpen(function(res) { console.log('WebSocket连接已打开!'); }); uni.onSocketClose((res) => { uni.showToast({ title: '与服务器连接中断,请重新刷新页面!', icon: 'loading' }) window.clearInterval(this.t1); this.isLogin = false this.ST = false setTimeout(() => { this.open() }, 5000) }); uni.onSocketError((res) => { uni.showToast({ title: '与服务器连接错误,请重新刷新页面!', icon: 'loading' }) this.isLogin = false this.ST = false setTimeout(() => { this.open() }, 5000) }); }, }
