Skip to content

React-Naitve WebSocket业务拓展分享:自动重连操作、心跳包代码 #9

Open
@1uokun

Description

@1uokun

Install

npm i react-native-reconnecting-websocket

概览

在React-Naitve中调用的WebSocket并不是调用web或者NodeJs中的那个WebSocket API,只是使用方式被设计得一样而已。详见源代码 react-native/WebSocket.js

WebSocket,以下简称WS,目前自带有onOpenonMessageonCloseonError四种监听事件以及close()send()两种方法,
当发生网路故障或者服务器故障时会断开链接,这时再重新链接WS时需要开发者再次实例化WS,如下reconnect()方法

var ws = new WebSocket();
ws.onClose(){
  ws.reconnect()
}

ws.reconnect(){
  ws = new WebSocket()
}

如果又断开时需要延迟一段时间重试以减少开销,因此还要在onClose方法中继续拓展

ws.onClose(){
  setTimeout(function(){
    ws.reconnect()
  },3000)
}

在某些场景中,WS可能永远不会再被重连,我们还要考虑添加重连次数限制

ws.onOpen(){
  clearTimeout(timeout)
  reconnectAttempts  = 0;  //重连次数归零
}

ws.onClose(){
  if(maxReconnectAttempts === reconnectAttempts){
    //超出重连限制时禁止重连
    return false
  }
  timeout = setTimeout(function(){
    reconnectAttempts++;
    ws.reconnect()
  })
}

仅仅是新增重连机制,就要在onOpenonClose中添加这么多代码,还没有考虑到代码的鲁棒性。对于RN外包团队来说,很难做到多项目复用,即使复用也使得代码变得冗余臃肿,既然RN的WS也是模仿Web端进行设计的,那么如果能仅拓展源代码将会使得业务更加模块化开发。

React-Native/Libraries/WebSocket.js

通过打印console.log(new WebSocket())可以看到有一个属性_subscriptions,里面是所有注册的监听事件(上述说到的四种事件)。可以看出,RN在处理监听事件上是用的发布订阅事件模型,详见 WebSocketEvent.js,基于此,我们可以在_subscriptions中增加自定义事件达到不污染业务代码onCloseonOpen

如何拓展

详见 https://github.com/React-Sextant/react-native-reconnecting-websocket

基本原理

class ReconnectingWebSocket extends WebSocket{

}

github上有对Web端进行拓展的包joewalnes/reconnecting-websocket,但不兼容RN。

心跳包

1.日常开发代码

import ReconnectingWebSocket from "react-native-reconnecting-websocket"

ws = new ReconnectingWebSocket("ws://...");

ws.onopen = (e) => {
    console.log("onopen",e)
};
ws.onmessage = (evt) => {
    console.log("onmessage",JSON.parse(evt.data))
};
ws.onclose = (e) => {
    console.log("onclose",e)
};
ws.onerror = (e) => {
    console.log("onerror",e)
};

// add listen connecting event
// @params reconnectAttempts 尝试重连的次数
ws.onconnecting = (reconnectAttempts) => {
    console.log("onconnecting", reconnectAttempts)
}

2.断网联网监听

NetInfo.addEventListener('connectionChange',(info) => {
  if(info.type == 'none' || info.type == 'unknown') {
     heartCheck.reset();
     ws.close();
  }else {
     heartCheck.reset();
     ws.reconnect();
  }
});

3.添加心跳包heartbeat

ws.onopen = (e) => {
    // execute immediately!
    ws.send("heartbeat string");
    
    heartCheck.reset().start()
};

ws.onmessage = (evt) => {
    heartCheck.reset().start()
};

var heartCheck = {
    timeout: 10000,//default 10s
    timeoutObj: null,
    serverTimeoutObj: null,
    reset:function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start:function(){
        let self = this;
        this.timeoutObj = setTimeout(function(){
            if(ws.readyState === ws.OPEN){
              ws.send("heartbeat string");
            }
            self.serverTimeoutObj = setTimeout(function(){
                ws.reconnect(); // 重连
            }, self.timeout)
        }, this.timeout)
    }
}

别忘了务必在卸载组件前清除定时器!

componentWillUnmount(){
  // 清除定时器
  heartCheck.reset();
  // 主动关闭ws连接
  ws && typeof ws.close === "function" && ws.close();
}

关于心跳定时器和ws状态

  1. 如果ws状态为CONNECTING时,使用ws.send()会使应用崩溃INVALID_STATE_ERR
    所以要这样使用send才安全:
     if(ws.readyState !== ws.CONNECTING){
        ws.send(heartStr)
     }
  2. 如果ws状态为CLOSEING或者CLOSED,即非OPEN使,使用ws.send()会使socket error

对于react-native-reconnecting-websocket而言,只有在onerror时才会自动重连,onclose时不会。所以当心跳返回发生超时时,我们有两种方案:

  1. 使用ws.reconnect()主动重连
     start:function(){
      let self = this;
      this.timeoutObj = setTimeout(function(){
          if(ws.readyState === ws.OPEN){ //只在OPEN状态发送心跳
            ws.send("heartbeat string");
          }
          self.serverTimeoutObj = setTimeout(function(){
              ws.reconnect(); // 本库提供的
          }, self.timeout)
      }, this.timeout)
     }
  2. 非OPEN状态下也使用ws.send()主动使socket error,触发重连
     start:function(){
       let self = this;
       this.timeoutObj = setTimeout(function(){
           if(ws.readyState !== ws.CONNECTING){ //只要不是CONNECING都发送
             ws.send("heartbeat string");
           }
           self.serverTimeoutObj = setTimeout(function(){
               ws.close();
           }, self.timeout)
       }, this.timeout)
      }
    
    //监听到close时重置定时器以触发`ws.send()`
    ws.onclose = ()=>{
      heartCheck.reset().start()
    }

其他

安卓原生module包

感兴趣的还可以直接修改原生包

package com.facebook.react.modules.websocket

参考

Metadata

Metadata

Assignees

No one assigned

    Labels

    blog日常开发笔记

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions