赞
踩
参考:
如何使用websocket
WebSocket客户端连接不上和掉线的问题以及解决方案
继6月份对websocket一顿了解之后,我们的项目也要上websocket了,虽然这部分不是我做,但是借此机会,我也想要尝试一下:假如是我来写这个模块,我会写成什么样呢?
抱着这样的想法,在11月份开始着手查了一些资料,打算完整地写一份前端的socket实例,一开始完全没有头绪,网上查了查大家写的都很简略的样子,达不到想要的效果,后来写小程序的时候想到可以借鉴小程序里的websocket封装,然后终于确定了类的api和基本功能。
修改过几次后,实现的功能和流程如下:(当然我自己写的这一份只是在自己的网站里做了一些简单测试,里面肯定还有很多bug待解决的)
前端代码实现如下:
后端是使用express-ws简单地建立了一个本地服务器,代码不贴了吧,网上老多了。
const READY_STATE = { CONNECTING: 'CONNECTING', OPEN: 'OPEN', CLOSING: 'CLOSING', CLOSED: 'CLOSED', 0: 'CONNECTING', 1: 'OPEN', 2: 'CLOSING', 3: 'CLOSED', }; /** * eg: new Socket('/connect',{success:(ws)=>{}, fail:(e)=>{switch(e.code){}}, onMessage:(res)=>{} }) * onclose 如果允许重连则重连,不允许则直接回调fail * onerror 仅在后台发出警示,回调中不开放这个回调,一切在fail函数中处理 * @param url 需要连接的ws链接,仅 /+pathname host拼接 * @param success 连接成功后调用 * @param fail 连接失败 除了以下code,其他为未知bug * code=1 -> 不支持websocket * code=2 -> 不允许重连-连接失败、允许重连-重连三次后也还是连接失败 * code=3 -> 身份验证未通过 * code=4 -> 未收到心跳事件主动断开连接 * code=5 -> (暂时不处理) 因为客户端断线 / 服务端主动断开等原因导致连接关闭后的回调 * @param onMessage 收到消息的回调函数。 * @param allowReConnect 允许连接失败后再重新连接而不立刻返回失败结果。(比如结果页就不需要重连,只需要第一次连接的结果,否则重连几次后,支付结果都出来了) * 连接成功后将发送身份验证,每次连接发送数据需要带上本次会话的发送时间戳(不然前端无法确定本次回复是哪次消息) * ? 如果意外在同一个页面同时创建了多个链接,前端无法检测出来,因为对象之间互相不联系,需要后端查询是否有相同用户链接,主动断开。 */ class Socket { constructor(connectUrl = '', options = {}) { this.initData(connectUrl, options); this.connect(); } initData = (connectUrl, options) => { this.connectUrl = connectUrl; this.props = options; // socket连接事件 this.socketOpen = false; this.socketConnecting = false; this.socketMsgQueue = []; // 待发送、发送后未收到回复 的消息队列 this.maxSocketConnectCount = 3; // 最多断线重连3次 this.socketConnectCount = 0; // 断线重连次数 this.socketConnectTimer = null; // 断线重连定时器 // 心跳事件 this.heartBeatFailCount = 0; // 心跳连接失败次数 this.heartBeatTimer = null; // 心跳事件定时器 this.heartBeatEventCb = null; // 心跳事件回调函数,一旦回应则清空,未回应则重连或关闭连接。 }; /** * 建立连接 * @param {string} from reconnet | undefined 默认为初始化连接,reconnet为连接失败重连 */ connect = (from = 'connect') => { try { if (!window.WebSocket) { this.options.fail({ code: 1, error: '不支持' }); } const ws = new WebSocket('ws://localhost:8100' + this.connectUrl); this.socketConnecting = true; // 注册ws相关事件 ws.onopen = () => { console.log('success'); this.ws = ws; this.socketOpen = true; this.socketConnecting = false; this.cancelReConnect(); this.ping(); }; ws.onerror = (e) => { this.socketOpen = false; this.socketConnecting = false; console.error('ERROR:' + from + '-连接失败'); }; ws.onclose = () => { console.error('CLOSE:' + from + '-连接失败'); this.socketOpen = false; this.cancelHeartBeat(); if(this.socketConnecting){ ws.close(); this.socketConnecting = false; } if (this.props.allowReConnect) { this.reConnect(); } else { this.props.fail?.({ code: 2, error: '连接失败' }); } }; } catch (error) { this.options.fail({ code: 1000, error }); } }; /** 取消重连 */ cancelReConnect = () => { if (this.socketConnectTimer) { clearTimeout(this.socketConnectTimer); this.socketConnectTimer = null; this.socketConnectCount = 0; } }; /** 重连 */ reConnect = () => { if (this.socketConnectCount < this.maxSocketConnectCount) { this.socketConnectCount = this.socketConnectCount + 1; this.socketConnectTimer = setTimeout(()=>{ this.connect(); },1000) } else { this.socketConnectCount = 0; clearTimeout(this.socketConnectTimer); this.socketConnectTimer = null; } }; onmessage = (e, cb) => { if (e.data === 'pong') { // 清空当前的心跳回调事件 clearTimeout(this.heartBeatEventCb); this.heartBeatEventCb = null; } console.log('from server: ' + e.data); cb(e); }; // 关闭心跳事件 cancelHeartBeat = () => { clearInterval(this.heartBeatTimer); this.heartBeatTimer = null; }; // 注册心跳事件 ping = () => { if (this.heartBeatTimer) { this.cancelHeartBeat(); } this.heartBeatTimer = setInterval(() => { this.ws.send('ping'); // 向心跳事件列表push一个心跳事件,30秒发送一次,setTimeOut 10s后没有收到Pong消息则重连或关闭连接。 this.heartBeatEventCb = setTimeout(()=>{ this.ws.close(); this.props.fail({code:4,error:'未收到心跳事件'}) },1 * 10 * 1000) }, 1 * 30 * 1000); }; } export default Socket;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。