Saltar al contenido

Fiabilidad del transporte de Websocket (pérdida de datos de Socket.io durante la reconexión)

Después de tanto batallar ya encontramos la respuesta de esta impedimento que agunos lectores de nuestra web presentan. Si deseas aportar algún dato puedes dejar tu conocimiento.

Solución:

Otros han insinuado esto en otras respuestas y comentarios, pero el problema principal es que Socket.IO es solo un mecanismo de entrega, y usted no poder dependa solo de él para una entrega confiable. La única persona que sabe con certeza que un mensaje se ha entregado correctamente al cliente. es el propio cliente. Para este tipo de sistema, recomendaría hacer las siguientes afirmaciones:

  1. Los mensajes no se envían directamente a los clientes; en cambio, se envían al servidor y se almacenan en algún tipo de almacén de datos.
  2. Los clientes son responsables de preguntar “qué me perdí” cuando se reconectan y consultarán los mensajes almacenados en el almacén de datos para actualizar su estado.
  3. Si se envía un mensaje al servidor mientras el cliente receptor está conectado, ese mensaje se enviará en tiempo real al cliente.

Por supuesto, dependiendo de las necesidades de su aplicación, puede ajustar partes de esto; por ejemplo, puede usar, digamos, una lista de Redis o un conjunto ordenado para los mensajes, y borrarlos si sabe con certeza que un cliente está activo. hasta la fecha.


Aquí hay un par de ejemplos:

Camino feliz:

  • U1 y U2 están conectados al sistema.
  • U2 envía un mensaje al servidor que debería recibir U1.
  • El servidor almacena el mensaje en algún tipo de almacenamiento persistente, marcándolo para U1 con algún tipo de marca de tiempo o ID secuencial.
  • El servidor envía el mensaje a U1 a través de Socket.IO.
  • El cliente de U1 confirma (quizás a través de una devolución de llamada Socket.IO) que recibió el mensaje.
  • El servidor elimina el mensaje persistente del almacén de datos.

Ruta sin conexión:

  • U1 pierde la conectividad a Internet.
  • U2 envía un mensaje al servidor que debería recibir U1.
  • El servidor almacena el mensaje en algún tipo de almacenamiento persistente, marcándolo para U1 con algún tipo de marca de tiempo o ID secuencial.
  • El servidor envía el mensaje a U1 a través de Socket.IO.
  • Cliente de U1 no confirmar la recepción, porque están desconectados.
  • Quizás U2 envíe a U1 algunos mensajes más; todos se almacenan en el almacén de datos de la misma manera.
  • Cuando U1 se vuelve a conectar, le pregunta al servidor “El último mensaje que vi fue X / Tengo el estado X, ¿qué me perdí?”
  • El servidor envía a U1 todos los mensajes que perdió del almacén de datos según la solicitud de U1
  • El cliente de U1 confirma la recepción y el servidor elimina esos mensajes del almacén de datos.

Si desea absolutamente una entrega garantizada, entonces es importante diseñar su sistema de tal manera que estar conectado no importe realmente, y que la entrega en tiempo real sea simplemente un prima; esto casi siempre implica un almacén de datos de algún tipo. Como mencionó el usuario568109 en un comentario, existen sistemas de mensajería que abstraen el almacenamiento y la entrega de dichos mensajes, y puede valer la pena buscar una solución prediseñada de este tipo. (Es probable que aún tenga que escribir la integración de Socket.IO usted mismo).

Si no está interesado en almacenar los mensajes en la base de datos, puede salirse con la suya almacenándolos en un local. array; el servidor intenta enviar el mensaje a U1 y lo almacena en una lista de “mensajes pendientes” hasta que el cliente de U1 confirma que lo recibió. Si el cliente está desconectado, cuando regrese puede decirle al servidor “Hey, me desconectaron, por favor envíeme cualquier cosa que me haya perdido” y el servidor puede iterar a través de esos mensajes.

Afortunadamente, Socket.IO proporciona un mecanismo que permite a un cliente “responder” a un mensaje que se parece a las devoluciones de llamada nativas de JS. Aquí hay un pseudocódigo:

// server
pendingMessagesForSocket = [];

function sendMessage(message) 
  pendingMessagesForSocket.push(message);
  socket.emit('message', message, function() 
    pendingMessagesForSocket.remove(message);
  
;

socket.on('reconnection', function(lastKnownMessage) 
  // you may want to make sure you resend them in order, or one at a time, etc.
  for (message in pendingMessagesForSocket since lastKnownMessage) 
    socket.emit('message', message, function() 
      pendingMessagesForSocket.remove(message);
    
  
);

// client
socket.on('connection', function() 
  if (previouslyConnected) 
    socket.emit('reconnection', lastKnownMessage);
   else 
    // first connection; any further connections means we disconnected
    previouslyConnected = true;
  
);

socket.on('message', function(data, callback) 
  // Do something with `data`
  lastKnownMessage = data;
  callback(); // confirm we received the message
);

Esto es bastante similar a la última sugerencia, simplemente sin un almacén de datos persistente.


También puede interesarle el concepto de abastecimiento de eventos.

La respuesta de Michelle es bastante acertada, pero hay algunas otras cosas importantes a considerar. La pregunta principal que debe hacerse es: “¿Hay alguna diferencia entre un usuario y un socket en mi aplicación?” Otra forma de preguntar es “¿Puede cada usuario que haya iniciado sesión tener más de 1 conexión de socket a la vez?”

En el mundo web, es probable que siempre exista la posibilidad de que un solo usuario tenga múltiples conexiones de socket, a menos que haya establecido específicamente algo que lo evite. El ejemplo más simple de esto es si un usuario tiene abiertas dos pestañas de la misma página. En estos casos, no le importa enviar un mensaje / evento al usuario humano solo una vez … debe enviarlo a cada instancia de socket para ese usuario para que cada pestaña pueda ejecutar sus devoluciones de llamada para actualizar el estado de la interfaz de usuario. Tal vez esto no sea una preocupación para ciertas aplicaciones, pero mi instinto dice que lo sería para la mayoría. Si esto le preocupa, siga leyendo …

Para resolver esto (asumiendo que está utilizando una base de datos como su almacenamiento persistente) necesitaría 3 tablas.

  1. usuarios, que es un 1 a 1 con personas reales
  2. clientes, que representa una “pestaña” que podría tener una sola conexión a un servidor de socket. (cualquier ‘usuario’ puede tener múltiples)
  3. mensajes: un mensaje que debe enviarse a un cliente (no un mensaje que debe enviarse a un usuario oa un socket)

La tabla de usuarios es opcional si su aplicación no la requiere, pero el OP dijo que tienen una.

La otra cosa que debe definirse correctamente es “¿qué es una conexión de socket?”, “¿Cuándo se crea una conexión de socket?”, “¿Cuándo se reutiliza una conexión de socket?”. El psudocódigo de Michelle hace que parezca que una conexión de socket se puede reutilizar. Con Socket.IO, NO PUEDEN reutilizarse. He visto ser la fuente de mucha confusión. Hay escenarios de la vida real en los que el ejemplo de Michelle tiene sentido. Pero tengo que imaginarme que esos escenarios son raros. Lo que realmente sucede es que cuando se pierde una conexión de socket, esa conexión, ID, etc. nunca se reutilizarán. Por lo tanto, cualquier mensaje marcado específicamente para ese socket nunca se entregará a nadie porque cuando el cliente que se había conectado originalmente, se vuelve a conectar, obtiene una conexión completamente nueva y una nueva ID. Esto significa que depende de usted hacer algo para rastrear clientes (en lugar de sockets o usuarios) a través de múltiples conexiones de socket.

Entonces, para un ejemplo basado en la web, aquí estaría el conjunto de pasos que recomendaría:

  • Cuando un usuario carga un cliente (generalmente una sola página web) que tiene el potencial de crear una conexión de socket, agregue una fila a la base de datos de los clientes que está vinculada a su ID de usuario.
  • Cuando el usuario realmente se conecte al servidor de socket, pase el ID de cliente al servidor con la solicitud de conexión.
  • El servidor debe validar que el usuario tiene permiso para conectarse y que la fila del cliente en la tabla de clientes está disponible para la conexión y permitir / denegar en consecuencia.
  • Actualice la fila del cliente con el ID de socket generado por Socket.IO.
  • Envíe todos los elementos de la tabla de mensajes relacionados con la identificación del cliente. No habría ninguna en la conexión inicial, pero si se trataba de un cliente que intentaba reconectarse, puede haber alguna.
  • Cada vez que sea necesario enviar un mensaje a ese socket, agregue una fila en la tabla de mensajes que esté vinculada al ID de cliente que generó (no al ID del socket).
  • Intente emitir el mensaje y escuche al cliente con el acuse de recibo.
  • Cuando obtenga el reconocimiento, elimine ese elemento de la tabla de mensajes.
  • Es posible que desee crear alguna lógica en el lado del cliente que descarte los mensajes duplicados enviados desde el servidor, ya que esto es técnicamente una posibilidad, como algunos han señalado.
  • Luego, cuando un cliente se desconecta del servidor de socket (intencionalmente o por error), NO elimine la fila del cliente, simplemente borre el ID de socket como máximo. Esto se debe a que ese mismo cliente podría intentar volver a conectarse.
  • Cuando un cliente intenta volver a conectarse, envíe el mismo ID de cliente que envió con el intento de conexión original. El servidor verá esto como una conexión inicial.
  • Cuando se destruye el cliente (el usuario cierra la pestaña o se aleja), es cuando elimina la fila del cliente y todos los mensajes para este cliente. Este paso puede resultar un poco complicado.

Porque el último paso es complicado (al menos solía serlo, no he hecho nada de eso en mucho tiempo), y porque hay casos como pérdida de energía en los que el cliente se desconecta sin limpiar la fila del cliente y nunca lo intenta. para volver a conectarse con esa misma fila de cliente, probablemente desee tener algo que se ejecute periódicamente para limpiar cualquier cliente obsoleto y filas de mensajes. O bien, puede almacenar permanentemente todos los clientes y mensajes para siempre y marcar su estado de manera adecuada.

Entonces, para que quede claro, en los casos en que un usuario tiene dos pestañas abiertas, agregará dos mensajes idénticos a la tabla de mensajes, cada uno marcado para un cliente diferente porque su servidor necesita saber si cada cliente los recibió, no solo cada usuario.

Aquí tienes las comentarios y calificaciones

Si conservas algún incógnita y forma de prosperar nuestro artículo eres capaz de dejar una crítica y con mucho gusto lo estudiaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *