WebSocket:如何在其断开连接后自动重新连接

175
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
  ws.send(JSON.stringify({
      .... some message the I must send when I connect ....
  }));

};

ws.onmessage = function (e) {
  console.log('Got a message')
  console.log(e.data);
};

ws.onclose = function(e) {  
  console.log('socket closed try again'); 

}

ws.onerror = function(err) {
  console.error(err)
};

当我第一次连接到套接字时,必须先向服务器发送一条消息以进行身份验证并订阅频道。

我的问题是,有时套接字服务器不可靠,这会触发'ws'对象的onerroronclose事件。

问题:什么是一个好的设计模式,可以使我在套接字关闭或遇到错误时等待10秒钟,然后重新连接到套接字服务器(并重新发送初始消息到服务器)?


clear-ws 可以自动处理重新连接(如果您提供非空的 reconnect.delayMs)。 - grabantot
12个回答

305

这就是我最终得出的结果,它能够满足我的需求。

function connect() {
  var ws = new WebSocket('ws://localhost:8080');
  ws.onopen = function() {
    // subscribe to some channels
    ws.send(JSON.stringify({
        //.... some message the I must send when I connect ....
    }));
  };

  ws.onmessage = function(e) {
    console.log('Message:', e.data);
  };

  ws.onclose = function(e) {
    console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
    setTimeout(function() {
      connect();
    }, 1000);
  };

  ws.onerror = function(err) {
    console.error('Socket encountered error: ', err.message, 'Closing socket');
    ws.close();
  };
}

connect();

7
它是否重新连接到之前连接的同一个 WebSocket?因为我正在使用 WebSocket ID 来发送消息,但如果它有新的 WebSocket ID,则很难将消息发送到特定系统。 - Vishnu Y S
31
@AlexanderDunaev,超时时间主要添加是为了在服务器不可用(例如网络中断或本地调试服务器关闭)时避免过于激进的重新连接方式。但总的来说,我认为立即重新连接后指数增长的等待时间比固定的1秒等待更好一些。 - user658991
27
当 WebSocket 连接关闭时,WebSocket 实例会发生什么?它会被垃圾回收,还是浏览器会建立一个未使用的对象堆积? - knobo
19
使用setTimeout(connect, 1000)是一种更简洁、资源更有效的延迟重新连接的方式。此外,考虑使用setTimeout(connect, Math.min(10000, timeout += timeout)),在第一次连接前和每次成功连接后将超时重置为250。这样,在连接期间出现错误时会添加退避时间,但如果是一次性错误情况,则会快速重新连接-250、500、1000、2000、4000、8000、10000、10000毫秒的延迟比1000、1000、1000毫秒更不激进,但响应更快。 - unsynchronized
10
我发现这段代码存在一个问题,如果连接关闭并尝试重新打开连接失败,则我们将永远不会进行重试。 - Michael Connor
显示剩余9条评论

9

使用 setInterval 对我很有用,因为客户端连接可能会断开。

ngOnInit(): void {
    if (window.location.protocol.includes('https')) {
        this.protocol = 'wss';
    }

    this.listenChanges();
}


listenChanges(): void {
    this.socket = new WebSocket(`${this.protocol}://${window.location.host}/v1.0/your/url`);

    this.socket.onmessage = (event): void => {
        // your subscription stuff
        this.store.dispatch(someAction);
    };

    this.socket.onerror = (): void => {
        this.socket.close();
    };


    this.socket.onopen = (): void => {
        clearInterval(this.timerId);

        this.socket.onclose = (): void => {
            this.timerId = setInterval(() => {
                this.listenChanges();
            }, 10000);
        };
    };
}

当套接字被打开时,不要忘记调用clearInterval


8

这不是一个明确的React问题,但这里有一个React风格的答案:

TLDR:您可以使用setInterval定期检查WebSocket连接状态,并在连接关闭时尝试重新连接。https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState

class TestComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};

    this.connect = this.connect.bind(this);
  }

  componentDidMount() {
    this.interval = setInterval(this.connect, 1000);
  }

  componentWillUnmount() {
    if (this.ws) this.ws.close();
    if (this.interval) clearInterval(this.interval);
  }

  connect() {
    // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
    if (this.ws === undefined || (this.ws && this.ws.readyState === 3)) {
      this.ws = new WebSocket(`ws://localhost:8080`);

      this.ws.onmessage = (e) => {
        console.log(JSON.parse(e.data));
      };
    }
  }

  render() {
    return <div>Hey!</div>;
  }
}

4

1
这是否会重新连接订阅,还是需要手动重新订阅频道? - Mr. Kenneth

3

使用async-await,如果服务器关闭或发生任何错误,客户端将尝试每5秒自动重新连接,直到连接成功为止。请参考我的答案


1

更新后的回答:

最终,我发现(如果你不使用Java),最好实现自己的“ping/pong”策略。(如果你正在使用Java,请查看ping/pong“操作类型”,我记得不是很清楚...)

  1. 客户端每5秒向服务器发送“ping”。
  2. 一旦服务器收到“ping”,应该向客户端回复“pong”。
  3. 如果客户端在5秒内没有收到“pong”,则应重新连接服务器。

不要依赖任何第三方库。

警告:不要使用以下工具:(原因:它们不可靠、不稳定,而且功能非常有限。)

  1. 检查网络是否可用:https://github.com/hubspot/offline
  2. 重新连接:https://github.com/joewalnes/reconnecting-websocket

4
这个 GitHub 库 https://github.com/joewalnes/reconnecting-websocket 实际上可以作为 new WebSocket() 的一个简单替代品来使用。虽然这个答案有点偏离主题,但为了简单起见,在一个简单的连接中使用上述 JavaScript 库是可行的。 - TechnicalChaos
1
是的,你说得对!不要使用那两个 Github 存储库。 - Siwei
4
为什么我们不应该使用它们?第二个看起来非常有用。 - Shamoon
3
你应该执行你的ping/pong策略。不要依赖于开启/关闭事件。 - Siwei
1
请注意,截至我撰写本文时,ReconnectingWebSocket 不支持 'binaryType' 选项:它似乎50%的时间会回退到'blob',而经过最小化的 JS 根本不包含该功能。 因此,我只好自己动手了。 - disconnectionist
@Spankied 因为它们不可靠、不稳定,而且工作方式非常有限。使用它们有点浪费时间。 - Siwei

1

ReconnectingWebSocket是一个小库,通过提供一个API兼容的装饰的WebSocket类来解决这个问题,它会自动重新连接。

将脚本添加到您的页面中(例如通过<script>标签),并按照上面链接的README中的描述操作:

It is API compatible, so when you have:

var ws = new WebSocket('ws://....');

you can replace with:

var ws = new ReconnectingWebSocket('ws://....');

1
这会重新连接订阅还是需要重新订阅频道? - Mr. Kenneth

1
我在过去的几周里一直苦恼于这个问题,决定创建一个名为SuperSocket的软件包 - 以防对其他人有所帮助!它应该可以作为本地WebSocket的替代品。我发现的现有软件包似乎没有得到维护。
SuperSocket建立在现有的WebSocket实现之上,并且除了其他功能外,还确保重新连接直到成功。当然,你可以设置最大重试次数以避免无限循环和不必要的CPU负载 :)
//native implementation
var ws = new WebSocket('ws://localhost:8080');

//drop in replacement, embedding reconnect strategies
var ws = new SuperSocket('ws://localhost:8080');

很好!期待使用这个套餐!问题是,8080端口是必需的吗? - LukyVj
1
感谢您的反馈!不,您可以设置任何您想要的端口 - HTTP、TCP和WS实际上可以使用相同的端口! - BaptisteC

0

试试这个:

const observable = Observable.create(
  (obs: Observer<MessageEvent>) => {
    this.ws.onmessage = obs.next.bind(obs);
    this.ws.onerror = obs.error.bind(obs);
    // this.ws.onclose = obs.complete.bind(obs);
    this.ws.onclose = function () {
      window.location.reload()
    }
    return this.ws.close.bind(this.ws);
  });

const observer = {
  next: (data: Object) => {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }
};

和组件

getDatas() {
let url = environment.apiwebsocket
this.webSocketService.connect(url)
  .subscribe(evt => {
    let jsonObj = JSON.parse(evt.data)
  });}

0

这是我在项目中使用的简单版本。它包括一个递增的等待计时器以进行重新连接。

//wsURL - the string URL of the websocket
//waitTimer - the incrementing clock to use if no connection made
//waitSeed - used to reset the waitTimer back to default on a successful connection
//multiplier - how quickly you want the timer to grow on each unsuccessful connection attempt

const openSocket = (wsURL, waitTimer, waitSeed, multiplier) =>{
  let ws = new WebSocket(wsURL);
  console.log(`trying to connect to: ${ws.url}`);

  ws.onopen = () => {
      console.log(`connection open to: ${ws.url}`);
      waitTimer = waitSeed; //reset the waitTimer if the connection is made
      
      ws.onclose = () => {
        console.log(`connection closed to: ${ws.url}`);
        openSocket(ws.url, waitTimer, waitSeed, multiplier);
      };
      
      ws.onmessage = (message) => {
        //do something with messge...
      };
  };
  
  ws.onerror = () => {
    //increaese the wait timer if not connected, but stop at a max of 2n-1 the check time
    if(waitTimer < 60000) waitTimer = waitTimer * multiplier; 
    console.log(`error opening connection ${ws.url}, next attemp in : ${waitTimer/1000} seconds`);
    setTimeout(()=>{openSocket(ws.url, waitTimer, waitSeed, multiplier)}, waitTimer);
  }
}

openSocket(`ws://localhost:3000`, 1000, 1000, 2)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接