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?

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

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

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.


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.


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

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


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

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

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.