Reconnexion du client lors du redémarrage du serveur dans WebSocket

93

J'utilise Web socket en utilisant PHP5 et le navigateur Chrome en tant que client. J'ai pris le code du site http://code.google.com/p/phpwebsocket/ .

Je lance le serveur et le client est également connecté. Je peux aussi discuter. Maintenant, lorsque je redémarre le serveur (en le tuant et en le redémarrant), le client obtient les informations déconnectées, mais ne se reconnecte pas automatiquement au serveur lorsque j'envoie le message.

Comment y parvenir? Comme lorsque j'obtiens les informations déconnectées, dois-je les vérifier et les envoyer à JavaScript pour actualiser la page ou me reconnecter?

siddhusingh
la source

Réponses:

143

Lorsque le serveur redémarre, la connexion Web Socket est fermée, de sorte que l' oncloseévénement JavaScript est déclenché. Voici un exemple qui tente de se reconnecter toutes les cinq secondes.

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);
    };
}
Andrew
la source
3
J'espérais qu'il y avait une manière plus élégante, sans construire un nouvel objet et définir des actions d'événement ...
ciembor
3
Après 5 minutes, le navigateur se fige. Suis-je le seul?
Marc
16
Vous devez ajouter "ws = null;" avant setTimeout () pour éviter de multiplier les objets ws et eventHandligs
Max
8
Corrigez-moi si je me trompe, mais ce code est un peu dangereux car un certain nombre de déconnexions entraînera un débordement de pile. C'est parce que vous appelez startrécursivement, sans jamais revenir.
Forivin
10
@Forivin Aucun problème de stackoverflow ici. Comme il n'y a qu'un seul thread en Javascript exécutant notre code à un moment donné, setTimeout () planifie la fonction passée pour être exécutée dans le futur lorsque ce thread unique est à nouveau libre. Une fois setTimeout () appelé ici, le thread revient de la fonction (effacement de la pile), puis va traiter l'événement suivant dans la file d'attente. Il finira par arriver à notre fonction anonyme qui appelle start et qui sera appelée comme cadre supérieur de la pile.
Stinky
39

La solution proposée par Andrew ne fonctionne pas parfaitement car, en cas de perte de connexion, le serveur peut envoyer plusieurs événements de fermeture.

Dans ce cas, vous définirez plusieurs setTimout. La solution donnée par Andrew ne peut fonctionner que si le serveur est prêt avant cinq secondes.

Ensuite, sur la base de la solution Andrew, retravaillée, j'ai utilisé setInterval en attachant l'ID à l'objet window (de cette façon, il est disponible "partout"):

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 */
 /* ... */
}
user2909737
la source
vous pouvez toujours l'utiliser setTimeoutsi vous leur appliquez l'idée "global timer id";)
RozzA
3
"La solution donnée par Andrew ne peut fonctionner que si le serveur est prêt avant cinq secondes." - La déclaration n'est pas vraie. Si le serveur est toujours indisponible après cinq secondes, votre client ne parviendra pas à ouvrir une connexion WebSocket et l' oncloseévénement sera déclenché à nouveau.
Sourav Ghosh
36

Reconnexion de WebSocket

GitHub héberge une petite bibliothèque JavaScript qui décore l'API WebSocket pour fournir une connexion WebSocket qui se reconnectera automatiquement si la connexion est interrompue.

La bibliothèque réduite avec compression gzip est inférieure à 600 octets.

Le référentiel officiel est disponible ici:

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

Inondation de serveurs

Si un nombre élevé de clients sont connectés au serveur lors du redémarrage. Il peut être intéressant de gérer les délais de reconnexion des clients en utilisant un algorithme de réduction exponentielle.

L'algorithme fonctionne comme ceci:

  1. Pour k tentatives, générer un intervalle de temps aléatoire entre 0 et 2 ^ k - 1,
  2. Si vous parvenez à vous reconnecter, réinitialisez k à 1,
  3. Si la reconnexion échoue, k augmente de 1 et le processus redémarre à l'étape 1,
  4. Pour tronquer l'intervalle max, lorsqu'un certain nombre de tentatives k a été atteint, k cesse d'augmenter après chaque tentative.

Référence:

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

ReconnectingWebSocket ne gère pas les reconnexions à l'aide de cet algorithme.

Joël Esponde
la source
Excellente réponse, en particulier parce qu'elle mentionne le risque d'une charge élevée du serveur une fois que le serveur ferme les connexions de socket Web et que tous les clients (qui peuvent être des centaines ou des milliers) essaient de se reconnecter en même temps. Au lieu d'une interruption exponentielle, vous pouvez également randomiser le délai entre 0 et 10 secondes. Cela répartira également la charge sur le serveur.
Jochem Schulenklopper
30

J'utilise ce modèle depuis un certain temps pour du JavaScript purement vanille, et il prend en charge quelques cas de plus que les autres réponses.

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


});

Cela réessaiera dès que le serveur fermera la connexion, et il vérifiera la connexion pour s'assurer qu'elle est également active toutes les 5 secondes.

Donc, si le serveur n'est pas opérationnel lorsque cela s'exécute ou au moment de l'événement onclose, la connexion sera toujours rétablie une fois qu'elle sera de nouveau en ligne.

REMARQUE: l'utilisation de ce script ne vous permettra pas d'arrêter d'essayer d'ouvrir une connexion ... mais je pense que c'est ce que vous voulez?

compliste
la source
7
Je changerais seulement: function check () {if (! Ws || ws.readyState === WebSocket.CLOSED) start (); }
dieresys
1
Cette approche, plus une technique de maintien en vie décrite ici , semble bien fonctionner pour moi.
Peter
@ Peter, je ne sais pas si l'état ws est ouvert, vous devez (ou devriez) envoyer un ping, si j'ai raison, il est déjà dans le protocole websocket. Cette surpuissance vient d'avoir une charge sur votre serveur ...
comte
@comte certains serveurs ws vous déconnectent après une «période d'inactivité» sans que les messages soient envoyés par le client et donc pour maintenir la connexion ouverte, le ping est une mauvaise nécessité.
RozzA
2

Voici les codes que j'ai utilisés dans mon projet qui fonctionnent à 100%.

  1. Mettez tout le code websocket dans la fonction init.
  2. À l'intérieur du rappel onclose, appelez à nouveau l'init.
  3. Appelez enfin la fonction init dans la fonction document ready.

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

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
la source
2

Je ne peux pas commenter, mais ce qui suit:

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

De plus, https://www.npmjs.com/package/back est déjà assez bon :)

xemasiv
la source
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
la source
0

Enfin, je fais une reconnexion automatique ws dans vue + ts, similaire à ce qui suit:

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
la source
0

L'événement de fermeture côté client pour WebSocket a une propriété wasClean , ce qui m'a été utile. On dirait qu'il est défini sur true dans les cas où l'ordinateur client passe en mode veille, etc. ou lorsque le serveur est arrêté de manière inattendue, etc. Il est défini sur false si vous fermez manuellement le socket, auquel cas vous ne voulez pas ouvrir la prise à nouveau automatiquement. Ci-dessous le code d'un projet Angular 7. J'ai ce code dans un service, il est donc utilisable à partir de n'importe quel composant.

    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
la source