Reconexión del cliente cuando el servidor se reinicia en WebSocket

93

Estoy usando un socket web usando PHP5 y el navegador Chrome como cliente. Tomé el código del sitio http://code.google.com/p/phpwebsocket/ .

Ejecuto el servidor y el cliente también está conectado. Yo también puedo charlar. Ahora, cuando reinicio el servidor (matándolo y volviéndolo a iniciar), el cliente obtiene la información desconectada, pero automáticamente no se vuelve a conectar con el servidor cuando envío el mensaje.

¿Cómo lograrlo? Como cuando obtengo la información desconectada, ¿debo verificarla y enviarla a JavaScript para actualizar la página o reconectarme?

siddhusingh
fuente

Respuestas:

143

Cuando el servidor se reinicia, la conexión Web Socket se cierra, por lo oncloseque se activa el evento de JavaScript . Aquí hay un ejemplo que intenta volver a conectarse cada cinco segundos.

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);
    };
}
Andrés
fuente
3
Esperaba que hubiera una forma más elegante, sin construir un nuevo objeto y definir acciones de eventos ...
ciembor
3
Después de 5 minutos, el navegador se congela. ¿Soy el único?
Marc
16
Debe agregar "ws = null;" antes de setTimeout () para evitar multiplicar los objetos ws y eventHandligs
Max
8
Corrígeme si me equivoco, pero este código es un poco peligroso, ya que una cierta cantidad de desconexiones provocará un desbordamiento de la pila. Eso es porque llamas de forma startrecursiva, sin volver jamás.
Forivin el
10
@Forivin No hay problema de desbordamiento de pila aquí. Dado que solo hay un solo hilo en Javascript que ejecuta nuestro código en un momento dado, setTimeout () programa la función pasada para que se ejecute en el futuro cuando ese único hilo esté libre nuevamente. Después de llamar a setTimeout () aquí, el hilo regresa de la función (limpiando la pila), luego va a procesar el siguiente evento en la cola. Eventualmente llegará a nuestra función anónima que comienzan las llamadas y que se llamará como el marco superior en la pila.
Apestoso
39

La solución dada por Andrew no funciona perfectamente porque, en caso de pérdida de conexión, el servidor puede enviar varios eventos cercanos.

En ese caso, establecerá varios setTimout. La solución dada por Andrew solo puede funcionar si el servidor está listo antes de cinco segundos.

Luego, en base a la solución de Andrew, reelaborada, utilicé setInterval adjuntando la ID al objeto de la ventana (de esa manera está disponible "en todas partes"):

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 */
 /* ... */
}
usuario2909737
fuente
aún puede usar setTimeoutsi les aplica la idea de "ID de temporizador global";)
RozzA
3
"La solución dada por Andrew sólo puede funcionar si el servidor está listo antes de cinco segundos". La afirmación no es cierta. Si el servidor aún no está disponible después de cinco segundos, su cliente no podrá abrir una conexión WebSocket y el oncloseevento se activará nuevamente.
Sourav Ghosh
36

Reconectando WebSocket

GitHub aloja una pequeña biblioteca de JavaScript que decora la API de WebSocket para proporcionar una conexión WebSocket que se volverá a conectar automáticamente si la conexión se interrumpe.

La biblioteca minimizada con compresión gzip tiene menos de 600 bytes.

El repositorio oficial está disponible aquí:

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

Inundación del servidor

Si hay una gran cantidad de clientes conectados al servidor cuando se reinicia. Puede ser útil administrar los tiempos de reconexión de los clientes mediante el uso de un algoritmo de retroceso exponencial.

El algoritmo funciona así:

  1. Para k intentos, genere un intervalo de tiempo aleatorio entre 0 y 2 ^ k - 1,
  2. Si puede volver a conectarse, restablezca k a 1,
  3. Si falla la reconexión, k aumenta en 1 y el proceso se reinicia en el paso 1,
  4. Para truncar el intervalo máximo, cuando se ha alcanzado un cierto número de intentos k, k deja de aumentar después de cada intento.

Referencia:

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

ReconnectingWebSocket no maneja las reconexiones usando este algoritmo.

Joël Esponde
fuente
Excelente respuesta, especialmente porque menciona el riesgo de una alta carga del servidor una vez que el servidor cierra las conexiones del socket web y todos los clientes (que podrían ser cientos o miles) intentan volver a conectarse al mismo tiempo. En lugar de un retroceso exponencial, también puede aleatorizar el retraso entre 0 y 10 segundos. Eso también distribuirá la carga en el servidor.
Jochem Schulenklopper
30

He estado usando este patrón durante un tiempo para JavaScript puro de vainilla, y admite algunos casos más que las otras respuestas.

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


});

Esto volverá a intentar tan pronto como el servidor cierre la conexión y verificará la conexión para asegurarse de que esté activa también cada 5 segundos.

Por lo tanto, si el servidor no está activo cuando esto se ejecuta o en el momento del evento de cierre, la conexión aún volverá una vez que esté en línea.

NOTA: El uso de este script no le permitirá dejar de intentar abrir una conexión ... pero creo que eso es lo que quiere.

complista
fuente
7
Solo cambiaría: function check () {if (! Ws || ws.readyState === WebSocket.CLOSED) start (); }
dieresys
1
Este enfoque, más una técnica de mantener vivo que se describe aquí , parece funcionar bien para mí.
Peter
@ Peter, no estoy seguro de si el estado de ws está abierto, necesita (o debería) hacer ping, si estoy en lo correcto, ya está en el protocolo websocket. Esta exageración acaba de cargarse en su servidor ...
comte
@comte, algunos servidores ws lo desconectan después de un 'período inactivo' sin que se envíen mensajes desde el cliente y, por lo tanto, para mantener la conexión abierta, el ping es una necesidad maligna.
RozzA
2

A continuación se muestran los códigos que he usado en mi proyecto que funcionan al 100%.

  1. Pon todo el código de websocket dentro de la función init.
  2. Dentro de la devolución de llamada onclose, llame al init nuevamente.
  3. Finalmente llame a la función init dentro de la función document ready.

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

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
fuente
2

No puedo comentar, pero lo siguiente:

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

Además, https://www.npmjs.com/package/back ya es suficientemente bueno :)

xemasiv
fuente
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
fuente
0

Finalmente, hago que ws se vuelva a conectar automáticamente en vue + ts, similar a lo siguiente:

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
fuente
0

El evento de cierre del lado del cliente para WebSocket tiene una propiedad wasClean , que fue útil para mí. Parece que se establece en verdadero en los casos en que la computadora cliente entra en modo de suspensión, etc. o cuando el servidor se detiene inesperadamente, etc. Se establece en falso si cierra manualmente el socket, en cuyo caso no desea abrir el enchufe automáticamente de nuevo. Debajo del código de un proyecto de Angular 7. Tengo este código en un servicio, por lo que se puede utilizar desde cualquier 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);   
        } 
    }
    .....
    .....

Leoncc
fuente