108

I am using web socket using PHP5 and the Chrome browser as client. I have taken the code from the site http://code.google.com/p/phpwebsocket/.

I run the server, and the client is also connected. I can chat as well. Now when I restart the server (by killing it and starting it again), the client gets the disconnected information, but automatically doesn't reconnect with server when I send the message.

How to achieve this? Like when I get the dis-connected information, should I check it and send it to JavaScript to refresh the page or reconnect?

Sandeep Patel
  • 4,815
  • 3
  • 21
  • 37
siddhusingh
  • 1,832
  • 4
  • 25
  • 30

9 Answers9

155

When the server reboots, the Web Socket connection is closed, so the JavaScript onclose event is triggered. Here's an example that tries to reconnect every five seconds.

function start(websocketServerLocation){
    ws = new WebSocket(websocketServerLocation);
    ws.onmessage = function(evt) { alert('message received'); };
    ws.onclose = function(){
        // Try to reconnect in 5 seconds
        setTimeout(function(){start(websocketServerLocation)}, 5000);
    };
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andrew
  • 227,796
  • 193
  • 515
  • 708
  • 6
    I hoped there is more elegant way, without constructing a new object and defining event actions... – ciembor Feb 10 '13 at 23:05
  • 4
    After 5 minutes, the browser freezes. Am I the only one? – Marc Apr 28 '16 at 12:06
  • 23
    You should add "ws = null;" before setTimeout() to avoid multiply ws objects and eventHandligs – Max Oct 03 '16 at 15:32
  • 10
    Correct me if I'm wrong, but this code is kind of dangerous as a certain amount of disconnects will cause a stack overflow. That is because you call `start` recursively, without ever returning. – Forivin Dec 03 '16 at 14:14
  • 1
    shouldn't the function be setInterval rather than setTimeout for client to reconnect every 5 seconds. Settimeout will only call once after the 5 seconds – Vinay Prabhakaran Apr 30 '17 at 12:44
  • 2
    @VinayPrabhakaran `onclose` will be invoked whenever the current connection is terminated, including the connections created from within the `onclose` handler. So, using setInterval would be excessive. – nagytech Jun 27 '17 at 22:10
  • 14
    @Forivin No stackoverflow issue here. Since there is only 1 single thread in Javascript executing our code at any given moment, setTimeout() schedules the passed function to be executed in the future when that single thread is free again. After setTimeout() is called here, the thread returns from the function (clearing the stack), then goes to process the next event in the queue. It will eventually get to our anonymous function that calls start and that will be called as the top frame in the stack. – phil-daniels Oct 07 '17 at 15:31
  • 2
    Avoid multiple instances of your timeouts running, by using a `global_timer_id` & `clearTimeout(global_timer_id)` before setting another. – RozzA May 24 '18 at 03:12
  • 1
    One thing that wasn't immediately obvious to me was that the `onclose` event will still fire even if the `new WebSocket` constructor throws an exception (e.g. due to server not being present) – EoghanM Nov 13 '19 at 17:19
  • @Max Is that still an issue if the `ws` variable is not set to `null` before assigned to a new WebSocket even if the `ws` is a global variable or at least defined outside the scope of where it is being set in the `start` function in this answer above. – sçuçu Mar 27 '21 at 21:52
45

The solution given by Andrew isn't perfectly working because, in case of lost connection, the server might send several close events.

In that case, you'll set several setTimout's. The solution given by Andrew may only work if the server is ready before five seconds.

Then, based on Andrew solution, reworked, I've made use of setInterval attaching the ID to the window object (that way it is available "everywhere"):

var timerID=0;

var socket;

/* Initiate what has to be done */

socket.onopen=function(event){
 /* As what was before */
 if(window.timerID){ /* a setInterval has been fired */
   window.clearInterval(window.timerID);
   window.timerID=0;
 }
 /* ... */
}

socket.onclose=function(event){
  /* ... */
 if(!window.timerID){ /* Avoid firing a new setInterval, after one has been done */
  window.timerID=setInterval(function(){start(websocketServerLocation)}, 5000);
 }
 /* That way, setInterval will be fired only once after losing connection */
 /* ... */
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user2909737
  • 615
  • 6
  • 5
  • you can still use `setTimeout` if you apply the "global timer id" idea to them ;) – RozzA May 24 '18 at 03:10
  • 4
    "The solution given by Andrew may only work if the server is ready before five seconds."--The statement is not true. If the server is still unavailable after five seconds, Your client will fail to open a WebSocket connection and the `onclose` event will be fired again. – Sourav Ghosh Aug 30 '19 at 11:13
41

ReconnectingWebSocket

There is a small JavaScript library that decorates the WebSocket API to provide a WebSocket connection that will automatically reconnect if the connection is dropped.

Minified library with gzip compression is less than 600 bytes.

The repository is available here:

https://github.com/joewalnes/reconnecting-websocket

There is also a TypeScript Library. Just include it and replace new WebSocket with new ReconnectingWebSocket.

The repository is available here:

https://github.com/pladaria/reconnecting-websocket

Server flood

If a high number of clients are connected to the server when it reboots. It may be worthwhile to manage the reconnect timings of the clients by using an Exponential Backoff algorithm.

The algorithm works like this:

  1. For k attempts, generate a random interval of time between 0 and 2^k - 1,
  2. If you are able to reconnect, reset k to 1,
  3. If reconnection fails, k increases by 1 and the process restarts at step 1,
  4. To truncate the max interval, when a certain number of attempts k has been reached, k stops increasing after each attempt.

Référence:

http://blog.johnryding.com/post/78544969349/how-to-reconnect-web-sockets-in-a-realtime-web-app

ReconnectingWebSocket does not handle reconnections by using this algorithm.

Joël Esponde
  • 535
  • 4
  • 10
  • 1
    Great answer, especially because it mentions the risk of a high server load once the server closes the web socket connections, and all clients (which could be hundreds or thousands) try to reconnect at the same time. Instead of exponential backoff, you could also randomize the delay like between 0 and 10 seconds. That will spread the load on the server as well. – Jochem Schulenklopper Mar 17 '17 at 15:03
  • @MartinSchilliger, I reedited the answer to keep the reference to the Javascript library and added a reference to the Typescript one you provided. If I correctly understood, Javascript code can be used inside Typescript code but not the other way around, that's why I think it is important to keep the reference to the Javascript library as it can help on more use cases. I hope that this change will be fine for you. – Joël Esponde Jan 19 '21 at 13:11
  • @JoëlEsponde Thank you. The TypeScript library also includes a JS version, so can be used with JS only also (I do that by myself). If I am right, the first library is suspended and only the TypeScript one is actively developed. – Martin Schilliger Jan 20 '21 at 14:03
  • @MartinSchilliger, thanks! I had a look into the typescript library and did not see any .js file implementing ReconnectingWebSocket. I just saw a .ts file. So, I guess that you got a javascript file after having compiled the .ts file ? AFAIK, web browsers do not support natively typescript files. – Joël Esponde Jan 22 '21 at 08:31
  • @JoëlEsponde the npm-package contains compiled .js files also in the /dist folder (as usual), so it can be used with JavaScript only. – Martin Schilliger Jan 23 '21 at 11:03
  • If you don't own your own server, throw best practices to the winds and bring everyone back on at once so they don't notice the outage. – 9pfs Mar 14 '21 at 21:15
35

I have been using this patten for a while for pure vanilla JavaScript, and it supports a few more cases than the other answers.

document.addEventListener("DOMContentLoaded", function() {

  'use strict';

  var ws = null;

  function start(){

    ws = new WebSocket("ws://localhost/");
    ws.onopen = function(){
      console.log('connected!');
    };
    ws.onmessage = function(e){
      console.log(e.data);
    };
    ws.onclose = function(){
      console.log('closed!');
      //reconnect now
      check();
    };

  }

  function check(){
    if(!ws || ws.readyState == 3) start();
  }

  start();

  setInterval(check, 5000);


});

This will retry as soon as the server closes the connection, and it will check the connection to make sure it's up every 5 seconds also.

So if the server is not up when this runs or at the time of the onclose event the connection will still come back once it's back online.

NOTE: Using this script will not allow you to ever stop trying to open a connection... but I think that's what you want?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
complistic
  • 2,610
  • 1
  • 29
  • 37
  • 8
    I would only change: function check(){ if(!ws || ws.readyState === WebSocket.CLOSED) start(); } – dieresys Jul 19 '16 at 17:33
  • 1
    This approach, plus a keep-alive technique described [here](http://www.jstips.co/en/javascript/working-with-websocket-timeout/), seems to works well for me. – Peter Jan 19 '18 at 17:19
  • @Peter, not sure if ws state is open you need to (or should) ping, if I'm correct it's already in websocket protocol. This overkill just had load on your server... – comte Apr 05 '18 at 22:07
  • @comte some ws servers disconnect you after an 'idle period' of no messages being sent from client & so to keep the connection open, the ping is an evil necessity. – RozzA May 24 '18 at 01:03
2

Below are the codes i have used in my project which working 100%.

  1. Put all the websocket code inside the init function.
  2. Inside the onclose callback call the init again.
  3. Finally call the init function inside the document ready function.

var name = sessionStorage.getItem('name');

wsUri =  "ws://localhost:8080";   
var websocket;
$(function() {  
    init();  
    $("#chat_text_box").on("keypress", function(e) {         
        if (e.keyCode == 13) {   //For Enter Button    
            e.preventDefault();
            var mymessage = $('#chat_text_box').val();               
            if(mymessage){
                var msg = {  type: 'chat_text',  data : {  name:name,  msg:mymessage }  };                
                console.log(msg);
                websocket.send(JSON.stringify(msg));
                $('#chat_text_box').val('');
            }               
            return false;                       
        }        
    });      
});     
function init() { 
    websocket = new WebSocket(wsUri);      
    websocket.onopen = function(ev) { /*connection is open */    } 
    websocket.onmessage = function(ev) {        
        var data = JSON.parse(ev.data); //PHP sends Json data        
        var type = data.type;//alert(JSON.stringify(data));
        switch(type) {
            case "chat_text":
                var text = "<div><span class='user'>"+data.data.sender_name+" : </span><span class='msg'>"+data.data.msg+"</span></div>";
                $('#chat-messages').append(text);
                break;            
            default:
                break;

        }        

    };     
    websocket.onerror   = function(ev){}; 
    websocket.onclose = function(ev) {   init();   };  
}
Pradeepta
  • 458
  • 3
  • 19
2

Can't comment, but the following:

var socket;

const socketMessageListener = (event) => {
  console.log(event.data);
};

const socketOpenListener = (event) => {
  console.log('Connected');
  socket.send('hello');
};

const socketCloseListener = (event) => {
  if (socket) {
    console.error('Disconnected.');
  }
  socket = new WebSocket('ws://localhost:8080');
  socket.addEventListener('open', socketOpenListener);
  socket.addEventListener('message', socketMessageListener);
  socket.addEventListener('close', socketCloseListener);
};

socketCloseListener();

// for testing
setTimeout(()=>{
  socket.close();
},5000);

Plus https://www.npmjs.com/package/back is already good enough :)

xemasiv
  • 87
  • 3
  • This one worked best for me. It plays well inside ReactJS. The others not so well. You can still put the socket creation part inside a 5 second timer to avoid it locking up the browser, especially with Inspect open. – blissweb Mar 17 '21 at 05:49
1

function wsConnection(url){
    var ws = new WebSocket(url);
    var s = (l)=>console.log(l);
 ws.onopen = m=>s(" CONNECTED")
    ws.onmessage = m=>s(" RECEIVED: "+JSON.parse(m.data))
    ws.onerror = e=>s(" ERROR")
    ws.onclose = e=>{
        s(" CONNECTION CLOSED");
        setTimeout((function() {
            var ws2 = new WebSocket(ws.url);
   ws2.onopen=ws.onopen;
            ws2.onmessage = ws.onmessage;
            ws2.onclose = ws.onclose;
            ws2.onerror = ws.onerror;
            ws = ws2
        }
        ).bind(this), 5000)
    }
    var f = m=>ws.send(JSON.stringify(m)) || "Sent: "+m;
    f.ping = ()=>ws.send(JSON.stringify("ping"));
    f.close = ()=>ws.close();
    return f
}

c=new wsConnection('wss://echo.websocket.org');
setTimeout(()=>c("Hello world...orld...orld..orld...d"),5000);
setTimeout(()=>c.close(),10000);
setTimeout(()=>c("I am still alive!"),20000);
<pre>
This code will create a websocket which will 
reconnect automatically after 5 seconds from disconnection.

An automatic disconnection is simulated after 10 seconds.
Zibri
  • 9,096
  • 3
  • 52
  • 44
1

The client side close event for WebSocket has a wasClean property, which was useful to me. Seems like it is set to true in cases where the client computer goes into sleep mode etc. or when the server is unexpectedly stopped etc. It is set to false if you manually close the socket, in which case you don't want to open the socket automatically again. Below code from an Angular 7 project. I have this code in a service, so it is usable from any component.

    notifySocketClose(event) { 

        if (!event.wasClean) { 
            setTimeout(() => {
                this.setupSocket()
            }, 1000);       
        }
    }

    setupSocket() { // my function to handle opening of socket, event binding etc.
    .....
    .....

            this.websocketConnection = this.websocketConnection ? this.websocketConnection : new WebSocket(socketUrl);
            this.websocketConnection.onclose = this.notifySocketClose.bind(this);   
        } 
    }
    .....
    .....

leoncc
  • 233
  • 3
  • 6
0

Finally, I make ws auto reconnect in vue+ts, similar as following:

private async mounted() {
    // Connect to WebSocket
    const sn = "sb1234567890";
    const host =
        window.location.protocol == "https:"
            ? "wss://www.xxx.net"
            : process.env.DEV_TYPE === "fullstack"
            ? "ws://10.0.0.14:8528"
            : "ws://www.xxx.net:8528";
    const wsUri = host + "/feed-home/" + sn;
    await this.startWs(wsUri, sn);
    // !!!Deprecated: failed to reconnect
    // let ws = new WebSocket();
    // console.log(ws);
    // ws.onopen = async function(event) {
    //     console.log(event, "openEvent");
    //     clearInterval(that.timer);
    // };
    // ws.onclose = async function(event) {
    //     console.log(event, "close");
    //     that.timer = setInterval(() => {
    //         console.log("Heart Beat");
    //         ws.send("HeartBeat");
    //         // ws = new WebSocket("ws://10.0.0.14:8528/feed-home/" + sn);
    //         console.log(ws);
    //     }, 60000);
    // };
    // ws.onmessage = async function(event) {
    //     console.log(event, "ws");
    //     alert("get it!");
    //     await alert("please update!");
    //     await that.getHome(sn);
    // };
}
private wsReconnected: boolean = false; // check whether WebSocket is reconnected
private async startWs(uri: string, sn: string) {
    let that = this;
    let ws = new WebSocket(uri);
    ws.onopen = async () => {
        if (that.wsReconnected) {
            await that.getHome(sn); // Refresh api data after reconnected
        }
        ws.send("Current client version: " + window.version);
    };
    ws.onmessage = async evt => {
        await that.getHome(sn);
        that.$message({
            type: "success",
            message: evt.data,
            showClose: true,
            center: true,
            duration: 20 * 1000
        });
    };
    ws.onclose = async () => {
        that.wsReconnected = true;
        await that.startWs(uri, sn);
        const sleep = (seconds: number) => {
            return new Promise(resolve =>
                setTimeout(resolve, seconds * 1000)
            );
        };
        await sleep(10); // Try to reconnect in 10 seconds
        // !!!Deprecated: Use timer will cause multiply ws connections
        // if (!that.wsTimer) {
        //     // Try to reconnect in 10 seconds
        //     that.wsTimer = setInterval(async () => {
        //         console.log("reconnecting websocket...");
        //         await that.startWs(uri, sn);
        //     }, 10 * 1000);
        // }
    };
}
Waket Zheng
  • 5,065
  • 2
  • 17
  • 30