目录

  • 预备代码
  • 解决方案
  • 断线重连
  • 心跳


预备代码

为描述方便,先将简单的 Websocket 连接函数 ws_connect() 贴出来,ws 为 Websocket 对象:

var ws;

/**
 * 连接 websocket
 * @param func onopen要执行的函数,可以为空
 */
function ws_connect(func) {
    ws = new WebSocket("ws://" + ws_ip);
    // 服务端主动推送消息时会触发这里的 onmessage
    ws.onmessage = function (e) {
        console.log('ws_onmessage ');
    };
    ws.onopen = function (e) {
        console.log('ws_onopen');
        // 开启心跳
        ws_heart();
        if (typeof func == 'function') {
            func();
        }
    };
    ws.onerror = function (e) {
        console.error("ws_onerror:", e);
    };
    ws.onclose = function (e) {
        console.log('ws_onclose:code:' + e.code + ';reason:' + e.reason + ';wasClean:' + e.wasClean);
    };
}

$(function () {
    var func = function () {
        var data = {type: 'login'};
        ws.send(JSON.stringify(data));
    };
	// 页面加载时第一次连接,也可以传空
    ws_connect(func);
});

解决方案

断线重连

重新连接的时候 Websocket 的属性 readyState 有着至关重要的作用,先了解一下这个属性的含义(以下其中之一):

  • 0 (CONNECTING) 正在链接中
  • 1 (OPEN) 已经链接并且可以通讯
  • 2 (CLOSING) 连接正在关闭
  • 3 (CLOSED) 连接已关闭或者没有链接成功

除了第一次连接,每一次连接时都必须考虑当前的连接状态,比如我要执行 ws.send(data); ,在四种状态下的执行时机是不同的,如下:

  • 0 (CONNECTING) 正在链接中 - 等连接成功后,在 ws.onopen 里执行
  • 1 (OPEN) 已经链接并且可以通讯 - 直接执行就是了
  • 2 (CLOSING) 连接正在关闭 - 等关闭完成后,在 ws.onclose 里重新连接,在重连成功的 ws.onopen 里执行
  • 3 (CLOSED) 连接已关闭或者没有链接成功 - 重新连接,在重连成功的 ws.onopen 里执行

这样每次发送数据都能保证连接成功(除非网络断了或服务器挂了),写一个函数 ws_execute() 封装这些操作,这个函数如下:

/**
 * 根据连接状态单线程连接 websocket
 * @param func onopen要执行的函数,可以为空
 */
function ws_execute(func) {
    console.log('ws_execute:readyState:' + ws.readyState);
    if (ws.readyState == 0) {
        // 正在链接中
        var _old$open = ws.onopen;
        ws.onopen = function (e) {
        	// 原本 onopen 里的代码先执行完毕
            _old$open.apply(this, arguments);
            if (typeof func == 'function') {
                func();
            }
        };
    } else if (ws.readyState == 1) {
        // 已经链接并且可以通讯
        if (typeof func == 'function') {
            func();
        }
    } else if (ws.readyState == 2) {
        // 连接正在关闭
        var _old$close = ws.onclose;
        ws.onclose = function (e) {
        	// 原本 onclose 里的代码先执行完毕
            _old$close.apply(this, arguments);
            ws_connect(func);
        };
    } else if (ws.readyState == 3) {
        // 连接已关闭或者没有链接成功
        ws_connect(func);
    }
}

业务逻辑里发送数据是这样的(代码片断):

// 发送数据时,将代码构造成函数作为参数,等 onopen 时执行
	var func = function () {
	    var data = {type: 'audio'};
	    ws.send(JSON.stringify(data));
	};
	ws_execute(func);

心跳

有了上面的 ws_execute() 函数,心跳就简单了,比如每1分钟向服务器发送一次数据:

var ws_heart_i = null;

/**
 * websocket 每1分钟发一次心跳
 */
function ws_heart() {
    if (ws_heart_i) clearInterval(ws_heart_i);
    ws_heart_i = setInterval(function () {
        console.log('ws_heart');
        var func = function () {
            var data = {type: 'ping'};
            ws.send(JSON.stringify(data));
        };
        ws_execute(func);
    }, 60000);
}

ws_heart() 函数放在 ws.onopen 里就可以了。