170
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)
};

When I first connect to the socket, I must first send a message to the server to authenticate myself and subscribe to channels.

The problem I have is that sometimes the socket server is unreliable and that triggers the onerror and onclose events of the 'ws' object.

Question: What is a good design pattern that would allow me, whenever the socket closes or encounters an error, wait for 10 seconds and then reconnect to the socket server (and resend the initial message to the server)

samol
  • 18,950
  • 32
  • 88
  • 127
  • 2
    Possible duplicate of [Reconnection of Client when server reboots in WebSocket](https://stackoverflow.com/questions/3780511/reconnection-of-client-when-server-reboots-in-websocket) – zoran404 Jun 01 '18 at 07:08
  • [clear-ws](https://www.npmjs.com/package/clear-ws) handles reconnects automatically (if you provide not null `reconnect.delayMs`). – grabantot Apr 25 '22 at 10:43

11 Answers11

298

Here is what I ended up with. It works for my purposes.

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();
halfer
  • 19,824
  • 17
  • 99
  • 186
samol
  • 18,950
  • 32
  • 88
  • 127
  • Just curious, what is the purpose of setTimeout? I have recently faced a similar problem, and it seems to be fine to call connect() directly from onclose handler. – Alexander Dunaev May 03 '17 at 08:08
  • 7
    Does that reconnect to the same websocket it was connected to before? Because I am using websocket id to send messages, but if it has new websocket id it would be hard to send messages to particular system. – Vishnu Y S May 11 '17 at 06:28
  • 31
    @AlexanderDunaev, the time out is mainly added as an easy way to avoid too aggressive reconnect when the server is not availble, i.e. broken network, or shutdown of local debug server. But in general, I think an immediate reconnect followed by exponentially growing wait time for reconnect would be slightly better choice than fixed 1sec wait. – user658991 Jun 27 '17 at 01:17
  • 1
    Had a similar approach but issues with the attached event handlers. Putting it all in a function is a clever way, thanks for that snippet. – F.H. Aug 23 '17 at 12:01
  • 27
    What happens to the websocket instance when the connection is closed. Is it garbage collected, or does the browser build up a pile of unused objects? – knobo Jun 21 '18 at 07:05
  • 2
    @VishnuYS It's better to create a specific channel for each user. Websocket id doens't work in different browser tabs. It will make one id per connection. – Karl Zillner Aug 23 '18 at 15:23
  • 18
    setTimeout(connect,1000) is a more concise, resource efficient way of delaying the reconnect. also consider using setTimeout (connect ,Math.min(10000,timeout+=timeout)), resetting timeout to 250 before first connect and after every successful connect. this way error conditions during connect will add a backoff, but will quickly reconnect if it is a one time error situation - 250,500,1000,2000,4000,8000,10000,10000 msec delays is less agressive, but faster responding than 1000,1000,1000 msec – unsynchronized May 08 '19 at 11:56
  • 9
    the issue i see with this code is that if the connection is closed and we try to open the connection again, and it fails, then we will never issue a retry. – Michael Connor Jul 31 '19 at 19:36
  • 3
    @MichaelConnor I think the retry attempt will trigger onerror again if it fails, which will trigger onclose, which will retry again (etc). – Malcolm Crum Jul 26 '21 at 09:03
  • 4
    @knobo: to have garbage collection I followed this answer (https://stackoverflow.com/a/11982071/1315873): add 'delete ws;' in onclose method before setTimeout call. Garbage collector will be called when memory is low. – Fil Aug 03 '21 at 22:50
  • 4
    I’ve used this method, but after a while I’ve noticed that each call to `connect()` will add a new connection request to the server queue, so when it’s up again, it might open hundreds/thousands of new connections that will crush the server at some point. – Ido Aug 24 '21 at 06:31
  • I accidentally misread this answer and put two `setTimeout` calls, one in `onclose` and one in `onerror`. I can assure you things will fail, since it will double the number of websockets on each interval, in my case freezing all my chrome windows – Amfasis Sep 10 '22 at 05:18
  • How to use the ws.send function? Do you return ws inside function then use it everywhere? If yes, during reconnect, ws object is already recreated by ws.send still points to old ws object. – Mr. Kenneth Nov 21 '22 at 10:42
  • Note that this doesn't currently work on webkit-based browsers, because the `close` event doesn't fire: https://bugs.webkit.org/show_bug.cgi?id=247943 – Mikael Finstad Jul 11 '23 at 18:37
10

This worked for me with setInterval, because client connection can be lost.

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);
        };
    };
}

Don't forget to call clearInterval when the socket has been opened.

Jakye
  • 6,440
  • 3
  • 19
  • 38
Bulat
  • 107
  • 1
  • 4
8

This isn't explicitly a react question but here is a react style answer:

TLDR: You can use setInterval to periodically check the websocket connection status and try to re-connect if the connection is closed. 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>;
  }
}
Glen Thompson
  • 9,071
  • 4
  • 54
  • 50
4

I found that this package https://github.com/pladaria/reconnecting-websocket can solve the reconnection issues for Websocket connections. And it has the list of configurable options, one of them is reconnectionDelayGrowFactor which determines how fast the reconnection delay grows.

3

using async-await if socket closed or any error occurred on the server the client will try to connect automatically every 5 sec forever have a look to my answer

Mohamed Farouk
  • 957
  • 1
  • 13
  • 29
1

UPDATED answer:

At last, (if you are not using java) I found you'd better implement your own "ping/pong" strategy. (if you are using java, please take a look at ping/pong "action type", I don't remember very clear... )

  1. client sent "ping" to server every 5 seconds.
  2. server should echo a "pong" to the client once it receive "ping".
  3. client should reconnect server if doesn't receive "pong" in 5 seconds.

Don't rely on any third party libs.

WARNING: DO NOT use these tools: (reason: they are not reliable and not stable and works in a very limited way. )

  1. check if the network is available: https://github.com/hubspot/offline
  2. to re-connect: https://github.com/joewalnes/reconnecting-websocket
Siwei
  • 19,858
  • 7
  • 75
  • 95
  • 4
    The github library https://github.com/joewalnes/reconnecting-websocket actually works as a simple drop in for `new WebSocket()` in a simple connection. I know this answer is a bit off the mark in general, but for simplicity, using the mentioned javascript library here does work. – TechnicalChaos Feb 04 '20 at 10:08
  • 1
    Yes you are right ! Don't use those 2 github repos. – Siwei Feb 09 '20 at 05:45
  • 4
    Why should we not use them? The second one looks pretty useful. – Shamoon May 28 '20 at 21:04
  • 3
    you should implement your ping/pong strategy. don't trust the open/close event . – Siwei May 29 '20 at 11:03
  • 1
    Note that as of my writing, ReconnectingWebSocket does not support the 'binaryType' option: it seems to fall back to 'blob' 50% of the time, and the minified JS does not contain the functionality at all. So I just rolled my own. – disconnectionist Dec 22 '20 at 15:22
  • @Spankied because they are not reliable and not stable and works in a very limit way. It's kinda of waste time to use them. – Siwei Aug 11 '21 at 09:54
1

ReconnectingWebSocket is a small library that addresses this by providing an API-compatible decorated WebSocket class that automatically reconnects.

Add the script to your page (e.g., via a <script> tag) and, as described by the README linked above:

It is API compatible, so when you have:

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

you can replace with:

var ws = new ReconnectingWebSocket('ws://....');
Ryan M
  • 18,333
  • 31
  • 67
  • 74
MD SHAYON
  • 7,001
  • 45
  • 38
1

I have been struggling with this the last few weeks and decided to create a package called SuperSocket - in case it helps anyone! It should work as a drop in replacement for the native WebSocket. Existing packages I found seemed unmaintained.

SuperSocket sits on top of the existing WebSocket implementation and, amongst other features, make sure it reconnects until it successfully does. Of course you can setup a max retry to avoid infinite loop and unnecessary CPU load :)

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

//drop in replacement, embedding reconnect strategies
var ws = new SuperSocket('ws://localhost:8080');
BaptisteC
  • 56
  • 2
  • Nice! Looking forward to use this package! Question, is the port 8080 mandatory? – LukyVj Aug 24 '23 at 08:24
  • 1
    Thank you for the feedback! No, you can set any ports you want - and HTTP, TCP and WS can actually use the same port! – BaptisteC Aug 24 '23 at 08:26
0

Try this:

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));
    }
  }
};

and component

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

I used to have this somewhere in project:

let rc = new WebSocket(
    'ws://'
    + window.location.host
    + `/ws/chat/${window.seen.pk}/`
)

now I switched to:

// ws create the websocket and returns it
function autoReconnect(ws_create){
    let ws = ws_create();
    function startReconnecting(){
        let interval = setInterval(()=>{
            console.log('trying')
            ws = ws_create();
            ws.onopen = () => {
                console.log('stop');
                ws.onclose = startReconnecting;
                clearInterval(interval);
            }
        }, 3000);
    }
    ws.onclose = startReconnecting;
}

let rc;
autoReconnect(()=>{
    rc = new WebSocket(
        'ws://'
        + window.location.host
        + `/ws/chat/${window.seen.pk}/`
    )
    return rc;
});

test it by running and stop local host, it works fine. (btw I found it weird this question has been posted for a long time, but there is not a short and elegant solution)

the benefit of this method, is that it allows you to pass in an arrow function, so that you can assign variable to any outer scope.

Weilory
  • 2,621
  • 19
  • 35
0

Here's a simple version I use in my projects. It includes an incrementing wait timer for reconnects.

//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)