tutorial socket node example con node.js socket.io

node.js - node - socket.io example



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

Usado

NodeJS, Socket.io

Problema

Imagina que hay 2 usuarios U1 y U2 , conectados a una aplicación a través de Socket.io. El algoritmo es el siguiente:

  1. U1 pierde completamente la conexión a Internet (por ejemplo, apaga Internet)
  2. U2 envía un mensaje a U1 .
  3. U1 aún no ha recibido el mensaje porque Internet está inactivo
  4. El servidor detecta la desconexión de U1 por tiempo de espera de latido
  5. U1 vuelve a conectar a socket.io
  6. U1 nunca recibe el mensaje de U2 , se pierde en el Paso 4, supongo.

Explicación posible

Creo que entiendo por qué sucede:

  • en el Paso 4, el servidor mata la instancia de socket y la cola de mensajes a U1 también
  • Además, en el Paso 5, U1 y el Servidor crean una nueva conexión (no se reutiliza), por lo que incluso si el mensaje todavía está en la cola, la conexión anterior se pierde de todos modos.

Necesitas ayuda

¿Cómo puedo prevenir este tipo de pérdida de datos? Tengo que usar latidos, porque no las personas cuelgan en la aplicación para siempre. También debo dar la posibilidad de volver a conectarme, porque cuando implemento una nueva versión de la aplicación quiero cero tiempo de inactividad.

PD: Lo que yo llamo "mensaje" no es solo un mensaje de texto que puedo almacenar en la base de datos, sino un valioso mensaje del sistema, cuya entrega debe estar garantizada, o la UI se equivoca.

¡Gracias!

Adición 1

Ya tengo un sistema de cuenta de usuario. Además, mi aplicación ya es compleja. Agregar estados sin conexión / en línea no ayudará, porque ya tengo este tipo de cosas. El problema es diferente.

Echa un vistazo al paso 2. En este paso, técnicamente no podemos decir si el U1 se desconecta , solo pierde la conexión, digamos por 2 segundos, probablemente debido a una mala conexión a Internet. Entonces, U2 le envía un mensaje, pero U1 no lo recibe porque Internet aún no funciona (paso 3). El paso 4 es necesario para detectar usuarios sin conexión, digamos, el tiempo de espera es de 60 segundos. Eventualmente, en otros 10 segundos, la conexión a Internet para U1 está activada y se vuelve a conectar a socket.io. Pero el mensaje de U2 se pierde en el espacio porque en el servidor U1 se desconectó por tiempo de espera.

Ese es el problema, no voy a ser 100% de entrega.

Solución

  1. Recoge un emisor (nombre y datos de emisión) en el usuario {}, identificado por emitID aleatorio. Enviar emitir
  2. Confirme la emisión en el lado del cliente (envíe la emisión de nuevo al servidor con emitID)
  3. Si está confirmado, elimine el objeto de {} identificado por emitID
  4. Si el usuario se ha vuelto a conectar: ​​marque {} para este usuario y realice un ciclo para ejecutar el Paso 1 para cada objeto en {}
  5. Cuando está desconectado y / o conectado a la descarga {} para el usuario si es necesario

    // Server const pendingEmits = {};

    socket.on (''reconexión'', () => resendAllPendingLimits); socket.on (''confirm'', (emitID) => {delete (pendingEmits [emitID]);});

    // Cliente socket.on (''algo'', () => {socket.emit (''confirmar'', emitID);});


He estado mirando estas cosas últimamente y creo que un camino diferente podría ser mejor.

Trate de buscar en el servicio de Azure el bus, las preguntas y el tema se ocupan de los estados fuera de línea. El mensaje espera a que el usuario regrese y luego recibe el mensaje.

Es un costo para ejecutar una cola pero es como $ 0.05 por millón de operaciones para una cola básica, por lo que el costo de desarrollo sería más de horas de trabajo necesario para escribir un sistema de cola. https://azure.microsoft.com/en-us/pricing/details/service-bus/

Y el bus azul tiene bibliotecas y ejemplos para PHP, C #, Xarmin, Anjular, Java Script, etc.

Así que el servidor envía un mensaje y no tiene que preocuparse por rastrearlos. El cliente puede usar el mensaje para enviarlo de vuelta, ya que los medios pueden manejar el equilibrio de carga si es necesario.


Lo que creo que quieres es tener un socket reutilizable para cada usuario, algo como:

Cliente:

socket.on("msg", function(){ socket.send("msg-conf"); });

Servidor:

// Add this socket property to all users, with your existing user system user.socket = { messages:[], io:null } user.send = function(msg){ // Call this method to send a message if(this.socket.io){ // this.io will be set to null when dissconnected // Wait For Confirmation that message was sent. var hasconf = false; this.socket.io.on("msg-conf", function(data){ // Expect the client to emit "msg-conf" hasconf = true; }); // send the message this.socket.io.send("msg", msg); // if connected, call socket.io''s send method setTimeout(function(){ if(!hasconf){ this.socket = null; // If the client did not respond, mark them as offline. this.socket.messages.push(msg); // Add it to the queue } }, 60 * 1000); // Make sure this is the same as your timeout. } else { this.socket.messages.push(msg); // Otherwise, it''s offline. Add it to the message queue } } user.flush = function(){ // Call this when user comes back online for(var msg in this.socket.messages){ // For every message in the queue, send it. this.send(msg); } } // Make Sure this runs whenever the user gets logged in/comes online user.onconnect = function(socket){ this.socket.io = socket; // Set the socket.io socket this.flush(); // Send all messages that are waiting } // Make sure this is called when the user disconnects/logs out user.disconnect = function(){ self.socket.io = null; // Set the socket to null, so any messages are queued not send. }

Entonces la cola de socket se conserva entre las desconexiones.

Asegúrese de que guarda cada propiedad de socket usuarios en la base de datos y haga que los métodos formen parte de su prototipo de usuario. La base de datos no importa, solo guárdela como quiera que haya estado guardando a sus usuarios.

Esto evitará el problema mencionado en Additon 1 al requerir una confirmación del cliente antes de marcar el mensaje como enviado. Si realmente lo desea, puede asignar a cada mensaje una identificación y hacer que el cliente envíe la identificación del mensaje a msg-conf , luego verifíquelo.

En este ejemplo, user es el user la plantilla desde el cual se copian todos los usuarios o les gusta el prototipo del usuario.

Nota: Esto no ha sido probado.



Otros han sugerido esto en otras respuestas y comentarios, pero el problema raíz es que Socket.IO es solo un mecanismo de entrega, y no puede depender solo de él para una entrega confiable. La única persona que sabe con seguridad que un mensaje se ha entregado con éxito 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 su lugar, 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é extrañé" 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, por ejemplo, una lista Redis o un conjunto ordenado para los mensajes, y eliminarlos si sabe a ciencia cierta si un cliente está activo. hasta la fecha.

Aquí hay un par de ejemplos:

Feliz camino

  • U1 y U2 están conectados al sistema.
  • U2 envía un mensaje al servidor que U1 debería recibir.
  • El servidor almacena el mensaje en algún tipo de almacén 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 fuera de línea :

  • U1 pierde conectividad a internet.
  • U2 envía un mensaje al servidor que U1 debería recibir.
  • El servidor almacena el mensaje en algún tipo de almacén 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 no confirma la recepción, porque están fuera de línea.
  • Quizás U2 envíe a U1 unos cuantos 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é extrañé?"
  • 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 usted quiere absolutamente una entrega garantizada, entonces es importante diseñar su sistema de tal manera que la conexión realmente no importe, y esa entrega en tiempo real es simplemente un bono ; esto casi siempre implica un almacén de datos de algún tipo. Como lo mencionó el usuario 568109 en un comentario, hay sistemas de mensajería que abstraen el almacenamiento y la entrega de dichos mensajes, y puede valer la pena buscar una solución de este tipo preinstalada. (Es probable que todavía tengas que escribir tú mismo la integración de Socket.IO).

Si no está interesado en almacenar los mensajes en la base de datos, es posible que pueda guardarlos en una matriz local; 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á fuera de línea, cuando vuelva, puede decirle al servidor "Oye, me desconectaron, por favor, envíenme cualquier cosa que perdí" y el servidor puede recorrer esos mensajes.

Afortunadamente, Socket.IO proporciona un mecanismo que permite a un cliente "responder" a un mensaje que se parece a 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 te puede interesar el concepto de abastecimiento de eventos .


Parece que ya tienes sistema de cuentas de usuario. Usted sabe qué cuenta está en línea / fuera de línea, puede manejar el evento de conexión / desconexión:

Entonces, la solución es agregar mensajes en línea / fuera de línea y fuera de línea en la base de datos para cada usuario:

chatApp.onLogin(function (user) { user.readOfflineMessage(function (msgs) { user.sendOfflineMessage(msgs, function (err) { if (!err) user.clearOfflineMessage(); }); }) }); chatApp.onMessage(function (fromUser, toUser, msg) { if (user.isOnline()) { toUser.sendMessage(msg, function (err) { // alert CAN NOT SEND, RETRY? }); } else { toUser.addToOfflineQueue(msg); } })