11月22, 2017

手写一个能重连、能心跳检测等的websocket组件(基于vue)

由于服务端没有使用现在比较成熟的socket.io,而是直接用websocket写的,所以前端也基于vue进行了简单的封装,需要的地方mixin即可。
大致实现以下需求:
1、断线重连,异常重连。
2、一直连接不上的话,自动重连时间间隔越来越长,防止服务端请求过多。
3、一定时间去进行心跳检测,防止由于运营商等的影响而使websocket断掉。

export default {
    data() {
        return {
            ws: null,
            lockReconnect: false, // 避免重复连接
            defaultReconnectIntervalTime: 1000, // 初始默认重连间隔时间
            reconnectIntervalTime: 1000, // 重连间隔时间(随着次数增多会修改)
            closeByServer: false, // 是否是服务端命令关闭的
            wsUrl: 'ws://xx.xx.xxx.xx:xxxx/ws', // websocket地址
            isLogin: false,
            heartCheck: { // 心跳检测参数
                timeout: 6000, // 60秒
                timeoutObj: null,
                serverTimeoutObj: null,
            },
        };
    },
    created() {
        this.createWebSocket(this.wsUrl);
    },
    methods: {
        createWebSocket(url) {
            try {
                this.ws = new WebSocket(url);
                console.log(this.ws);
                this.initWebSocketEventHandle();
                console.log('连接正常');
            } catch (ex) {
                this.reconnect(url); // 异常重连
                console.log('连接异常', ex);
            }
        },
        initWebSocketEventHandle() {
            const vm = this;
            const onopen = function () {
                const packet = { // 发送的包信息去检验身份(和服务端约定)
                    'type': 3, 'loginId': '1', 'deviceId': '2', 'appId': '1', 'value': '123',
                };
                this.isLogin = false;
                vm.ws.send(`CONNECT ${JSON.stringify(packet)}`);
            };
            const onclose = function () {
                if (!vm.closeByServer) {
                    // 不是服务端命令关闭的就需要重连
                    vm.reconnect(vm.wsUrl);
                } else {
                    console.log('服务端主动断开ws');
                }
            };
            const onerror = function () {
                console.log('ws发生错误');
                vm.reconnect(vm.wsUrl);
            };
            const onmessage = function (event) {
                // 如果获取到消息并且是登录状态,心跳检测重置
                // 拿到任何消息都说明当前连接是正常的
                // 以下内容是具体业务具体操作和和服务端进行约定

                if (this.isLogin) {
                    vm.heartCheckReset();
                    vm.heartCheckStart();
                }

                const data = JSON.parse(event.data);
                console.log('data', data);

                switch (data.type) {
                    case 1: // 连接并身份校验成功
                        this.isLogin = true;
                        console.log('连接并身份校验成功', data.message);
                        vm.closeByServer = false; // 连上,重置
                        vm.reconnectIntervalTime = vm.defaultReconnectIntervalTime; // 连上了,重置重连间隔时间
                        //  服务端校验登录成功,才需要发送心跳
                        vm.heartCheckReset();
                        vm.heartCheckStart();
                        break;
                    case 2:
                        // 通知消息,服务端给前端消息,前端通知客户端,
                        // 客户端通知前端(同时客户端自己去鸿雁获取消息),前端再发送消息给服务端(最好取消息中最大的id)
                        // 这里处理具体的业务,并向服务端发送确认消息,使服务端不再重发
                        console.log('收到消息', data.message);
                        vm.ws.send(`CONFIRM ${JSON.stringify({ 'messageId': '50' })}`);
                        break;
                    case 4:
                        console.log('心跳检测成功!');
                        break;
                    // 服务端告知客户端需要进行重连
                    case 7:
                        vm.createWebSocket(vm.wsUrl);
                        console.log('服务端告知客户端需要进行重连');
                        break;
                    case 8:  // 服务端命令客户端关闭连接(这种场景服务端还没处理实现)
                        console.log(data.message);
                        vm.ws.close();
                        vm.closeByServer = true;
                        break;
                    // 客户端发送服务端不识别的命令,服务端响应错误消息
                    case 9:
                        console.log('服务端识别命令错误', data);
                        // if (data.message && data.message.code === 'login.first') {
                        //     vm.reconnect(vm.wsUrl);
                        // }
                        break;
                    // 在长时间未收到客户端的心跳信息之后,服务端发起ping消息,
                    // 以期待客户端响应,如客户端无响应,在超时之后,会关闭连接。
                    case 10:
                        vm.ws.send('PONG');
                        break;
                    default:
                        console.log('未知type', data);
                }
            };
            this.ws = Object.assign(this.ws, {
                onopen,
                onclose,
                onerror,
                onmessage,
            });
        },
        reconnect(url) {
            const vm = this;
            if (this.lockReconnect) return;

            this.lockReconnect = true;
            // 没连接上会一直重连,设置延迟(并且一直增加,直到30分钟重连一次)避免请求过多
            if (this.reconnectIntervalTime > 30 * 60000) {
                this.reconnectIntervalTime = 30 * 60000;
            }
            console.log('this.reconnectIntervalTime', this.reconnectIntervalTime);

            setTimeout(() => {
                vm.createWebSocket(url);
                vm.lockReconnect = false;
            }, this.reconnectIntervalTime);

            this.reconnectIntervalTime *= 2;
        },
        heartCheckReset() { // 心跳检测重置
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
        },
        heartCheckStart() { // 心跳检测开始
            const vm = this;
            this.timeoutObj = setTimeout(() => {
                // 这里发送一个心跳,后端收到后,返回一个心跳消息,
                // onmessage拿到返回的心跳就说明连接正常
                vm.ws.send('PING'); // 和服务端约定的心跳信息(这里是发了一个'PING'过去)
                // 如果超过一定时间还没重置,说明后端主动断开了
                vm.serverTimeoutObj = setTimeout(() => {
                    // 如果onclose会执行reconnect,执行ws.close()就行了.
                    // 如果直接执行reconnect 会触发onclose导致重连两次
                    vm.ws.close();
                }, vm.heartCheck.timeout);
            }, this.heartCheck.timeout);
        },
    },
}

本文链接:http://blog.hiraetho.com/post/wesocket-byself.html

-- EOF --

Comments