32

In Node.js I'm using websockets/ws for a WebSocket connection. Below is the code for the client. Let's say the server socket we are connecting to goes down for a minute. The close event will fire, but what is the best way to reconnect to the socket whenever the socket on the server goes down or errors?

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

ws.on('open', function() {
    console.log('socket open');
});
ws.on('error', function() {
    console.log('socket error');
    // how do I reconnect to the ws after x minutes here?
});
ws.on('close', function() {
    console.log('socket close');
    // how do I reconnect to the ws after x minutes here?
});
Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
wwwuser
  • 6,282
  • 8
  • 52
  • 64

7 Answers7

49

Try this:

var reconnectInterval = x * 1000 * 60;
var ws;
var connect = function(){
    ws = new WebSocket('ws://localhost');
    ws.on('open', function() {
        console.log('socket open');
    });
    ws.on('error', function() {
        console.log('socket error');
    });
    ws.on('close', function() {
        console.log('socket close');
        setTimeout(connect, reconnectInterval);
    });
};
connect();

You get to use the original implementation without having to wrap it.

John Henry
  • 2,419
  • 2
  • 20
  • 22
12

I've used https://github.com/joewalnes/reconnecting-websocket/blob/master/reconnecting-websocket.js with success.

You should be able to do:

ws = new ReconnectingWebSocket('ws://....');
ws.reconnectInterval = 60000; // try to reconnect after 10 seconds
Trevor Dixon
  • 23,216
  • 12
  • 72
  • 109
  • 1
    Interesting. I'll look into it, but I'd prefer to use einaros' ws. – wwwuser Oct 30 '13 at 19:44
  • 2
    It's a client-side lib. You can still use the einaros lib on the server. – Trevor Dixon Oct 30 '13 at 19:48
  • Oops, I see what's going on. You're connecting to a websocket from node.js. This still may help. You could use it to wrap the einaros WebSocket. – Trevor Dixon Oct 30 '13 at 19:49
  • 1
    I'm not sure I'm understanding. How would you use it to wrap einaros WebSocket? Could you give an example? – wwwuser Oct 30 '13 at 19:57
  • 5
    Change ReconnectingWebSocket to be something like https://gist.github.com/trevordixon/7239401. (Lines 51 and 183 are new there.) Then, in your client code, do `var WebSocket = require('./ReconnectingWebSocket.js')` instead of `require('ws')`, and you'll be using the ws library wrapped in ReconnectingWebSocket. – Trevor Dixon Oct 30 '13 at 20:14
12

2018-Jan update

Reconnecting to a disconnected web socket is non-trivial, and best delegated to a library. The smallest and most actively maintained library for this purpose at the moment is reconnecting-websocket, which obsoletes joewalnes's library from the other answer. For Node.js specifically, you need to pass a constructor, such as WebSocket:

import WebSocket from 'ws';
import ReconnectingWebSocket from 'reconnecting-websocket';
const ws = new ReconnectingWebSocket('wss://some-feed.com', [], {
  constructor: WebSocket,
  connectionTimeout: ...,  // in milliseconds
  reconnectInterval: ...,
});
Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
  • 1
    how does this obsolete the Joewalnes library, it appears to me that they both try to resolve the same problem. Does pladaria's library have features/improvements that Joewalnes doesn't? Just curious and thanks. – LeanMan Feb 12 '22 at 06:09
9

using async-await if Socket closed or any error occurred on the server the client will try to connect automatically every 5 sec forever

const ws = require('ws')
let openedSocketFlag = null
const timeInterval = 5000
const port = 3000
const url = `ws://localhost:${port}`

function connect() {
  const client = new ws(url)
  return new Promise((resolve, reject) => {
    console.log('client try to connect...')

    client.on('open', () => {
      console.log('WEBSOCKET_OPEN: client connected to server at port %s', port)
      openedSocketFlag = true
      resolve(openedSocketFlag)
    })

    client.on('message', (data) => {
      console.log(data.toString())
    })

    client.on('close', (err) => {
      console.log('WEBSOCKET_CLOSE: connection closed %o', err)
      openedSocketFlag = false
      reject(err)
    })

    client.on('error', (err) => {
      console.log('WEBSOCKET_ERROR: Error', new Error(err.message))
      openedSocketFlag = false
      reject(err)
    })
  })
}

async function reconnect() {
  try {
    await connect()
  } catch (err) {
    console.log('WEBSOCKET_RECONNECT: Error', new Error(err).message)
  }
}

reconnect()

// repeat every 5 seconds
setInterval(() => {
  if (!openedSocketFlag) {
    reconnect()
  }
}, timeInterval)
Mohamed Farouk
  • 957
  • 1
  • 13
  • 29
1

After examining @Mohamed Farouk's answer, I believe that there is merit in using a promise to signify the status of the connection. Here's an example that takes a bit from that answer, and a bit from my original:

const address = "ws://localhost";
const reconnectInterval = x * 1000 * 60;
const ws = {};
const establishSocket = address => new Promise((resolve, reject)=>{
  const s = new WebSocket(address);
  s.on("open", ()=>{
    delete ws.reason;
    ws.socket = s;
    console.log('socket open');
  });
  s.on("error", ()=>console.log('socket error'));
  s.on("close", reject);
}).catch(async (reason)=>{
  ws.socket = null;
  ws.reason = reason;
  console.log('socket close');
  await new Promise(resolve=>setTimeout(resolve, reconnectInterval));
  establishSocket(address);
});
establishSocket(address);

I make no assertions as to which approach is better -- I just thought this was an interesting solution.

John Henry
  • 2,419
  • 2
  • 20
  • 22
0

You should consider a migration to socket.io.

  1. It has built-in auto-reconnect functionality. And you don't have to do anything for it. It is already enabled by default.
  2. Surprisingly, it is compatible with older browsers, even browsers that don't support native websockets.

The code of both is very similar, but the socket.io is perhaps just a little shorter. e.g. for the server code we used to write something like this:

const WebSocketServer = require('websocket').server
const ws = new WebSocketServer({ httpServer });
ws.on('request', (request) => onConnection(request));

function onConnectionRequest(request) {
  const connection = request.accept(null, request.origin);
  if (!connection) return;
  connection.on('message', (msg) => onMessageReceived(msg));
  connection.on('close', () => onConnectionClosed());
}

function onMessage(message) {
  if (message.type === 'utf8') {
    const data = message.utf8Data;
    const request = JSON.parse(data);
    // todo use request
  }
}

Socket.io code is very similar, but just a little shorter.

const io = require('socket.io')(httpServer);
io.on('connection', (socket) => onConnection(socket));

function onConnection(socket) {
  socket.on('message', (msg) => onMessage(msg));
  socket.on('disconnect', (reason) => onDisconnect(reason));
}

function onMessage(request) {
  // todo use request
}

However, do take in mind, that you also have to rewrite the client code. e.g. For Angular I use the ngx-socket-io plugin, which simplifies the code extremely.

bvdb
  • 22,839
  • 10
  • 110
  • 123
0

i am using 'websocket' in my client side react/nextjs , and here is the urls for the specific package : https://www.npmjs.com/package/websocket
https://github.com/theturtle32/WebSocket-Node

and i am using 'reconnecting-websocket' package to reconnect the client websocket to the server (which is Django channels) when the server is restarted . and here is the official package url :
https://www.npmjs.com/package/reconnecting-websocket

and here is a simple example usage :

import { w3cwebsocket as W3CWebSocket } from "websocket";
import ReconnectingWebSocket from 'reconnecting-websocket';


//inside react component - nextjs page :

const options = {
    WebSocket: W3CWebSocket, // custom WebSocket constructor
    connectionTimeout: 1000,
    maxRetries: 10,
};



const isServerSide = typeof window === "undefined";
let client
if(!isServerSide) {
    client = new ReconnectingWebSocket(`ws://127.0.0.1:8000/ws/`,[], options);
    client.binaryType = "arraybuffer";
}


useEffect(() => {
    client.onopen = () => {
        console.log('WebSocket Client Connected');
    };
    client.onerror = (error)  => {
        console.log("Connection Error: " , error);
    };

    client.onclose = (close) => {
        console.log('echo-protocol Client Closed', close);
    };

}, [])

this is just example and change it based on your case/requirements.
i hope this helpful

K.A
  • 1,399
  • 12
  • 24