Riconnessione del client al riavvio del server in WebSocket


93

Utilizzo il socket web utilizzando PHP5 e il browser Chrome come client. Ho preso il codice dal sito http://code.google.com/p/phpwebsocket/ .

Corro il server e anche il client è connesso. Anch'io posso chattare. Ora, quando riavvio il server (uccidendolo e riavviandolo), il client ottiene le informazioni disconnesse, ma automaticamente non si riconnette al server quando invio il messaggio.

Come ottenerlo? Ad esempio, quando ricevo le informazioni scollegate, dovrei controllarle e inviarle a JavaScript per aggiornare la pagina o ricollegarmi?

Risposte:


143

Quando il server si riavvia, la connessione Web Socket viene chiusa, quindi oncloseviene attivato l' evento JavaScript . Ecco un esempio che cerca di riconnettersi ogni cinque secondi.

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

3
Speravo ci fosse un modo più elegante, senza costruire un nuovo oggetto e definire azioni dell'evento ...
ciembor

3
Dopo 5 minuti, il browser si blocca. Sono l'unico?
Marc

16
Dovresti aggiungere "ws = null;" prima di setTimeout () per evitare di moltiplicare gli oggetti WS e eventHandligs
Max

8
Correggimi se sbaglio, ma questo codice è un po 'pericoloso in quanto una certa quantità di disconnessioni causerà un overflow dello stack. Questo perché chiami in startmodo ricorsivo, senza mai tornare.
Forivin

10
@Forivin Nessun problema di stackoverflow qui. Poiché c'è solo 1 singolo thread in Javascript che esegue il nostro codice in un dato momento, setTimeout () pianifica la funzione passata da eseguire in futuro quando quel singolo thread è di nuovo libero. Dopo che setTimeout () è stato chiamato qui, il thread ritorna dalla funzione (cancellando lo stack), quindi va a elaborare l'evento successivo nella coda. Alla fine arriverà alla nostra funzione anonima che avvia le chiamate e che verrà chiamata come primo frame nello stack.
Stinky

39

La soluzione fornita da Andrew non funziona perfettamente perché, in caso di perdita di connessione, il server potrebbe inviare diversi eventi di chiusura.

In tal caso, imposterai diversi setTimout. La soluzione fornita da Andrew potrebbe funzionare solo se il server è pronto entro cinque secondi.

Quindi, sulla base della soluzione di Andrew, rielaborata, ho utilizzato setInterval allegando l'ID all'oggetto finestra (in questo modo è disponibile "ovunque"):

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 */
 /* ... */
}

puoi ancora usare setTimeoutse applichi l'idea "global timer id";)
RozzA

3
"La soluzione fornita da Andrew potrebbe funzionare solo se il server è pronto entro cinque secondi." - L'affermazione non è vera. Se il server non è ancora disponibile dopo cinque secondi, il client non riuscirà ad aprire una connessione WebSocket e l' oncloseevento verrà generato nuovamente.
Sourav Ghosh

36

ReconnectingWebSocket

GitHub ospita una piccola libreria JavaScript che decora l'API WebSocket per fornire una connessione WebSocket che si riconnetterà automaticamente se la connessione viene interrotta.

La libreria minimizzata con compressione gzip è inferiore a 600 byte.

Il repository ufficiale è disponibile qui:

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

Inondazione del server

Se un numero elevato di client è connesso al server al riavvio. Può essere utile gestire i tempi di riconnessione dei client utilizzando un algoritmo di backoff esponenziale.

L'algoritmo funziona in questo modo:

  1. Per k tentativi, genera un intervallo di tempo casuale compreso tra 0 e 2 ^ k - 1,
  2. Se riesci a riconnetterti, reimposta k su 1,
  3. Se la riconnessione fallisce, k aumenta di 1 e il processo si riavvia al passaggio 1,
  4. Per troncare l'intervallo massimo, quando un certo numero di tentativi k è stato raggiunto, k smette di aumentare dopo ogni tentativo.

Riferimento:

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

ReconnectingWebSocket non gestisce le riconnessioni utilizzando questo algoritmo.


Ottima risposta, soprattutto perché menziona il rischio di un carico elevato del server una volta che il server chiude le connessioni del socket web e tutti i client (che potrebbero essere centinaia o migliaia) tentano di riconnettersi allo stesso tempo. Invece del backoff esponenziale, puoi anche randomizzare il ritardo tra 0 e 10 secondi. Ciò distribuirà anche il carico sul server.
Jochem Schulenklopper

30

Sto usando questo patten per un po 'per JavaScript puro e vanigliato e supporta alcuni casi in più rispetto alle altre risposte.

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


});

Questo riproverà non appena il server chiude la connessione e controllerà la connessione per assicurarsi che sia attiva anche ogni 5 secondi.

Quindi, se il server non è attivo quando viene eseguito o al momento dell'evento onclose, la connessione tornerà comunque una volta tornata online.

NOTA: l'uso di questo script non ti permetterà di smettere mai di provare ad aprire una connessione ... ma penso che sia quello che vuoi?


7
Vorrei solo cambiare: function check () {if (! Ws || ws.readyState === WebSocket.CLOSED) start (); }
dieresys

1
Questo approccio, oltre a una tecnica keep-alive descritta qui , sembra funzionare bene per me.
Peter

@Peter, non sono sicuro che lo stato WS sia aperto devi (o dovresti) eseguire il ping, se ho ragione è già nel protocollo websocket. Questo sovraccarico ha appena avuto carico sul tuo server ...
comte

@comte alcuni server WS ti disconnettono dopo un "periodo di inattività" in cui nessun messaggio viene inviato dal client e quindi per mantenere la connessione aperta, il ping è una necessità malvagia.
RozzA

2

Di seguito sono riportati i codici che ho utilizzato nel mio progetto che funziona al 100%.

  1. Metti tutto il codice websocket all'interno della funzione init.
  2. All'interno del callback onclose chiama di nuovo l'init.
  3. Infine chiama la funzione init all'interno della funzione document ready.

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

2

Non posso commentare, ma quanto segue:

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

Inoltre https://www.npmjs.com/package/back è già abbastanza buono :)


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.


0

Infine, creo la riconnessione automatica di ws in vue + ts, simile al seguente:

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

0

L'evento di chiusura lato client per WebSocket ha una proprietà wasClean , che mi è stata utile. Sembra che sia impostato su true nei casi in cui il computer client entra in modalità di sospensione ecc. O quando il server viene arrestato in modo imprevisto ecc. È impostato su false se chiudi manualmente il socket, nel qual caso non vuoi aprirlo automaticamente nuovamente la presa. Di seguito il codice da un progetto Angular 7. Ho questo codice in un servizio, quindi è utilizzabile da qualsiasi componente.

    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);   
        } 
    }
    .....
    .....
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.