前言

最近关于H5和APP的开发中使用到了webSocket,由于web/app有时候会出现网络不稳定或者服务端主动断开,这时候导致消息推送不了的情况,需要客户端进行重连。查阅资料后发现了一个心跳机制,也就是客户端间隔一段时间就向服务器发送一条消息,如果服务器收到消息就回复一条信息过来,如果一定时间内没有回复,则表示已经与服务器断开连接了,这个时候就需要进行重连。

被动断开则进行重连,主动断开的不重连。

说明:下图针对两个Tab项(Open Trades 和 Closed Trades),只希望在 tabIndex = 0 (Open Trades 高亮时)触发webSocket , 如果点击第二个栏目 , tabIndex = 1(Closed Trades高亮时)则主动关闭webSodket连接。

TabIndex = 0 时 ,被动断开则自动重连。

效果

  1. webScoket连接并接收推送的消息
  2. 将接收的消息转换成目标数据,并渲染
  3. 如果主动关闭,则不进行重连,监听关闭事件
  4. 显示已关闭,不重连
  5. 监听错误事件,比如地址,协议错误等,则会自动重连五次,五次重连仍失败后则需要进行手动重连
  6. 如果服务端主动断开,心跳机制会每隔一段时间发送一条数据给服务端,如果没有回复则会进行webScoket重连

代码

  1. 新建 socket.js , 将以下代码复制进去 ,向外暴露。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    import api from '@/common/js/config.js' // 接口Api,图片地址等等配置,可根据自身情况引入,也可以直接在下面url填入你的 webSocket连接地址
    class socketIO {
    constructor(data, time, url) {
    this.socketTask = null
    this.is_open_socket = false //避免重复连接
    this.url = url ? url : api.websocketUrl //连接地址
    this.data = data ? data : null
    this.connectNum = 1 // 重连次数
    this.traderDetailIndex = 100 // traderDetailIndex ==2 重连
    this.accountStateIndex = 100 // accountStateIndex ==1 重连
    this.followFlake = false // followFlake == true 重连
    //心跳检测
    this.timeout = time ? time : 15000 //多少秒执行检测
    this.heartbeatInterval = null //检测服务器端是否还活着
    this.reconnectTimeOut = null //重连之后多久再次重连
    }

    // 进入这个页面的时候创建websocket连接【整个页面随时使用】
    connectSocketInit(data) {
    this.data = data
    this.socketTask = uni.connectSocket({
    url: this.url,
    success: () => {
    console.log("正准备建立websocket中...");
    // 返回实例
    return this.socketTask
    },
    });
    this.socketTask.onOpen((res) => {
    this.connectNum = 1
    console.log("WebSocket连接正常!");
    this.send(data)
    clearInterval(this.reconnectTimeOut)
    clearInterval(this.heartbeatInterval)
    this.is_open_socket = true;
    this.start();
    // 注:只有连接正常打开中 ,才能正常收到消息
    this.socketTask.onMessage((e) => {
    // 字符串转json
    let res = JSON.parse(e.data);
    console.log("res---------->", res) // 这里 查看 推送过来的消息
    if (res.data) {
    uni.$emit('getPositonsOrder', res);
    }
    });
    })
    // 监听连接失败,这里代码我注释掉的原因是因为如果服务器关闭后,和下面的onclose方法一起发起重连操作,这样会导致重复连接
    uni.onSocketError((res) => {
    console.log('WebSocket连接打开失败,请检查!');
    this.socketTask = null
    this.is_open_socket = false;
    clearInterval(this.heartbeatInterval)
    clearInterval(this.reconnectTimeOut)
    uni.$off('getPositonsOrder')
    if (this.connectNum < 6) {
    uni.showToast({
    title: `WebSocket连接失败,正尝试第${this.connectNum}次连接`,
    icon: "none"
    })
    this.reconnect();
    this.connectNum += 1
    } else {
    uni.$emit('connectError');
    this.connectNum = 1
    }

    });
    // 这里仅是事件监听【如果socket关闭了会执行】
    this.socketTask.onClose(() => {
    console.log("已经被关闭了-------")
    clearInterval(this.heartbeatInterval)
    clearInterval(this.reconnectTimeOut)
    this.is_open_socket = false;
    this.socketTask = null
    uni.$off('getPositonsOrder')
    if (this.connectNum < 6) {
    this.reconnect();
    } else {
    uni.$emit('connectError');
    this.connectNum = 1
    }

    })
    }
    // 主动关闭socket连接
    Close() {
    if (!this.is_open_socket) {
    return
    }
    this.socketTask.close({
    success() {
    uni.showToast({
    title: 'SocketTask 关闭成功',
    icon: "none"
    });
    }
    });
    }
    //发送消息
    send(data) {
    console.log("data---------->", data);
    // 注:只有连接正常打开中 ,才能正常成功发送消息
    if (this.socketTask) {
    this.socketTask.send({
    data: JSON.stringify(data),
    async success() {
    console.log("消息发送成功");
    },
    });
    }
    }
    //开启心跳检测
    start() {
    this.heartbeatInterval = setInterval(() => {
    this.send({
    "traderid": 10260,
    "type": "Ping"
    });
    }, this.timeout)
    }
    //重新连接
    reconnect() {
    //停止发送心跳
    clearInterval(this.heartbeatInterval)
    //如果不是人为关闭的话,进行重连
    if (!this.is_open_socket && (this.traderDetailIndex == 2 || this.accountStateIndex == 0 || this
    .followFlake)) {
    this.reconnectTimeOut = setInterval(() => {
    this.connectSocketInit(this.data);
    }, 5000)
    }
    }
    /**
    * @description 将 scoket 数据进行过滤
    * @param {array} array
    * @param {string} type 区分 弹窗 openposition 分为跟随和我的
    */
    arrayFilter(array, type = 'normal', signalId = 0) {
    let arr1 = []
    let arr2 = []
    let obj = {
    arr1: [],
    arr2: []
    }
    arr1 = array.filter(v => v.flwsig == true)
    arr2 = array.filter(v => v.flwsig == false)
    if (type == 'normal') {
    if (signalId) {
    arr1 = array.filter(v => v.flwsig == true && v.sigtraderid == signalId)
    return arr1
    } else {
    return arr1.concat(arr2)
    }
    } else {
    if (signalId > 0) {
    arr1 = array.filter(v => v.flwsig == true && v.sigtraderid == signalId)
    obj.arr1 = arr1
    } else {
    obj.arr1 = arr1
    }
    obj.arr2 = arr2

    return obj
    }
    }
    }
    module.exports = socketIO
  2. 在入口文件中 将 socketIO 挂载在 Vue 原型上 , 也可以按需引入置顶页面
    1
    2
    import socketIO from '@/common/js/scoket.js'
    Vue.prototype.socketIo = new socketIO()
  3. 在需要用到webSocket的页面中使用如下方法(可根据自身业务需求进行整改)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    scoketClose() {
    this.socketIo.connectNum = 1
    const data = {
    "value1": "demo1"
    "value2": "demo2"
    }
    this.socketIo.send(data) // 这是给后端发送特定数据 关闭推送
    this.socketIo.Close() // 主动 关闭连接 , 不会重连
    },

    getWebsocketData() {
    // 要发送的数据包
    const data = {
    "value": "value1",
    "type": "type1"
    }
    // 打开连接
    this.socketIo.connectSocketInit(data)
    // 接收数据
    uni.$on("getPositonsOrder", (res) => {
    this.connect = true
    const {
    Code,
    data
    } = res
    if (Code == xxxx) {
    // 根据后端传过来的数据进行 业务编写。。。
    } else {

    }
    })
    // 错误时做些什么
    uni.$on("connectError", () => {
    this.connect = false
    this.scoketError = true
    })
    }
  4. 离开页面,记得断开连接。
    1
    2
    3
    4
    onUnload() {
    this.scoketClose()
    this.socketIo.traderDetailIndex = 100 // 初始化 tabIndex
    }

遇到问题

如果在使用中遇到什么问题 ,可以给我留言 ,看到留言后会在第一时间进行回复 。