NodeJS, Socket.io
Imaginez qu'il y a 2 utilisateurs 1 & 2, connectés à une application via Socket.io. L'algorithme est le suivant:
Je pense que je comprends pourquoi cela se produit:
Comment éviter ce type de perte de données? Je dois utiliser des battements de cœur, car je ne bloque pas les applications pour toujours. De plus, je dois toujours donner la possibilité de me reconnecter, car lorsque je déploie une nouvelle version de l'application, je ne veux aucun temps d'arrêt.
P.S. La chose que j'appelle "message" n'est pas seulement un message texte que je peux stocker dans la base de données, mais un message système précieux, dont la livraison doit être garantie, ou une interface utilisateur foireuse.
Merci!
J'ai déjà un système de compte utilisateur. De plus, ma candidature est déjà complexe. L'ajout de statuts hors ligne/en ligne n'aidera pas, car j'ai déjà ce genre de choses. Le problème est différent.
Découvrez l'étape 2. Sur cette étape, nous avons techniquement ne peut pas dire si U1 se déconnecte, il perd juste la connexion, disons pendant 2 secondes, probablement à cause d'un mauvais Internet. U2 lui envoie donc un message, mais U1 ne le reçoit pas car Internet est toujours en panne pour lui (étape 3). L'étape 4 est nécessaire pour détecter les utilisateurs hors ligne, disons, le délai d'attente est de 60 secondes. Finalement, dans une autre connexion Internet de 10 secondes pour U1 est en place et il se reconnecte à socket.io. Mais le message de U2 est perdu dans l'espace car sur le serveur U1 a été déconnecté par timeout.
Voilà le problème, je ne veux pas une livraison à 100%.
En cas de déconnexion ou/et de rinçage connecté {} pour l'utilisateur si nécessaire
// Constante du serveur en attenteEmits = {};
socket.on ('reconnexion', () => resendAllPendingLimits); socket.on ('confirm', (emitID) => {delete (pendingEmits [emitID]);});
// Client socket.on ('quelque chose', () => {socket.emit ('confirm', emitID);});
D'autres ont fait allusion à cela dans d'autres réponses et commentaires, mais le problème racine est que Socket.IO est juste un mécanisme de livraison, et vous ne pouvez pas dépendre de lui seul pour une livraison fiable. La seule personne qui sait avec certitude qu'un message a bien été remis au client est le client lui-même . Pour ce type de système, je recommanderais de faire les affirmations suivantes:
Bien sûr, selon les besoins de votre application, vous pouvez en régler certaines parties - par exemple, vous pouvez utiliser, par exemple, une liste Redis ou un ensemble trié pour les messages, et les effacer si vous savez pertinemment qu'un client est en place à ce jour.
Voici quelques exemples:
Chemin heureux :
Chemin hors ligne :
Si vous voulez absolument une livraison garantie, il est important de concevoir votre système de manière à ce que la connexion n'ait pas d'importance et que la livraison en temps réel soit simplement un bonus ; cela implique presque toujours un magasin de données d'une certaine sorte. Comme user568109 l'a mentionné dans un commentaire, il existe des systèmes de messagerie qui résument le stockage et la livraison desdits messages, et il peut être utile d'étudier une telle solution préconfigurée. (Vous devrez probablement encore écrire l'intégration Socket.IO vous-même.)
Si vous n'êtes pas intéressé par le stockage des messages dans la base de données, vous pourrez peut-être vous en sortir en les stockant dans un tableau local; le serveur essaie d'envoyer le message à U1 et le stocke dans une liste de "messages en attente" jusqu'à ce que le client de U1 confirme qu'il l'a reçu. Si le client est hors ligne, à son retour, il peut indiquer au serveur "Hé, j'étais déconnecté, envoyez-moi tout ce que j'ai manqué" et le serveur peut parcourir ces messages.
Heureusement, Socket.IO fournit un mécanisme qui permet à un client de "répondre" à un message qui ressemble à des rappels JS natifs. Voici un pseudocode:
// 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
});
Ceci est assez similaire à la dernière suggestion, simplement sans magasin de données persistant.
Vous pouvez également être intéressé par le concept de sourcing d'événements .
Il semble que vous disposiez déjà d'un système de compte utilisateur. Vous savez quel compte est en ligne/hors ligne, vous pouvez gérer l'événement de connexion/déconnexion:
La solution est donc d'ajouter des messages en ligne/hors ligne et hors ligne sur la base de données pour chaque utilisateur:
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);
}
})
La réponse de Michelle est à peu près exacte, mais il y a quelques autres choses importantes à considérer. La principale question à vous poser est: "Y a-t-il une différence entre un utilisateur et un socket dans mon application?" Une autre façon de demander est "Est-ce que chaque utilisateur connecté peut avoir plus d'une connexion socket à la fois?"
Dans le monde du Web, il est probablement toujours possible qu'un seul utilisateur dispose de plusieurs connexions de socket, sauf si vous avez spécifiquement mis en place quelque chose qui empêche cela. L'exemple le plus simple est celui où un utilisateur a deux onglets de la même page ouverts. Dans ces cas, vous ne vous souciez pas d'envoyer un message/événement à l'utilisateur humain une seule fois ... vous devez l'envoyer à chaque instance de socket pour cet utilisateur afin que chaque onglet puisse exécuter ses rappels pour mettre à jour l'état de l'interface utilisateur. Peut-être que ce n'est pas un problème pour certaines applications, mais mon instinct dit que ce serait pour la plupart. Si cela vous inquiète, lisez la suite ...
Pour résoudre ce problème (en supposant que vous utilisez une base de données comme stockage persistant), vous aurez besoin de 3 tables.
Le tableau des utilisateurs est facultatif si votre application n'en a pas besoin, mais l'OP a indiqué qu'il en avait un.
L'autre élément qui doit être correctement défini est "qu'est-ce qu'une connexion socket?", "Quand est-ce qu'une connexion socket est créée?", "Quand une connexion socket est-elle réutilisée?". Le psudocode de Michelle donne l'impression qu'une connexion de socket peut être réutilisée. Avec Socket.IO, ils NE PEUVENT PAS être réutilisés. J'ai vu être la source de beaucoup de confusion. Il existe des scénarios réels où l'exemple de Michelle a du sens. Mais je dois imaginer que ces scénarios sont rares. Ce qui se passe vraiment, c'est quand une connexion socket est perdue, que la connexion, l'ID, etc. ne seront jamais réutilisés. Ainsi, tous les messages marqués spécifiquement pour ce socket ne seront jamais remis à personne, car lorsque le client qui s'était connecté à l'origine se reconnecte, il obtient une toute nouvelle connexion et un nouvel ID. Cela signifie que c'est à vous de faire quelque chose pour suivre les clients (plutôt que les sockets ou les utilisateurs) sur plusieurs connexions de socket.
Donc, pour un exemple basé sur le Web, voici l'ensemble des étapes que je recommanderais:
Parce que la dernière étape est délicate (au moins c'était le cas, je n'ai rien fait de tel depuis longtemps), et parce qu'il y a des cas comme une coupure de courant où le client se déconnecte sans nettoyer la ligne client et n'essaye jamais pour vous reconnecter avec cette même ligne client - vous souhaiterez probablement avoir quelque chose qui s'exécute périodiquement pour nettoyer les lignes client et message périmées. Ou, vous pouvez simplement stocker de façon permanente tous les clients et messages pour toujours et simplement marquer leur état de manière appropriée.
Donc, pour être clair, dans les cas où un utilisateur a deux onglets ouverts, vous ajouterez deux messages identiques à la table des messages, chacun marqué pour un client différent, car votre serveur doit savoir si chaque client les a reçus, pas seulement chaque utilisateur.
Essayez cette liste d'émission de chat
io.on('connect', onConnect);
function onConnect(socket){
// sending to the client
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
// sending to all clients except sender
socket.broadcast.emit('broadcast', 'hello friends!');
// sending to all clients in 'game' room except sender
socket.to('game').emit('Nice game', "let's play a game");
// sending to all clients in 'game1' and/or in 'game2' room, except sender
socket.to('game1').to('game2').emit('Nice game', "let's play a game (too)");
// sending to all clients in 'game' room, including sender
io.in('game').emit('big-announcement', 'the game will start soon');
// sending to all clients in namespace 'myNamespace', including sender
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
// sending to individual socketid (private message)
socket.to(<socketid>).emit('hey', 'I just met you');
// sending with acknowledgement
socket.emit('question', 'do you think so?', function (answer) {});
// sending without compression
socket.compress(false).emit('uncompressed', "that's rough");
// sending a message that might be dropped if the client is not ready to receive messages
socket.volatile.emit('maybe', 'do you really need it?');
// sending to all clients on this node (when using multiple nodes)
io.local.emit('hi', 'my lovely babies');
};
Ce que je pense que vous voulez, c'est d'avoir un socket réutilisable pour chaque utilisateur, quelque chose comme:
Client:
socket.on("msg", function(){
socket.send("msg-conf");
});
Serveur:
// 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.
}
Ensuite, la file d'attente de socket est préservée entre les déconnexions.
Assurez-vous qu'il enregistre la propriété socket
de chaque utilisateur dans la base de données et intégrez les méthodes à votre prototype utilisateur. La base de données n'a pas d'importance, enregistrez-la simplement comme vous avez sauvegardé vos utilisateurs.
Cela évitera le problème mentionné dans l'additon 1 en exigeant une confirmation du client avant de marquer le message comme envoyé. Si vous le vouliez vraiment, vous pourriez attribuer un identifiant à chaque message et demander au client d'envoyer l'identifiant du message à msg-conf
, puis vérifiez-le.
Dans cet exemple, user
est l'utilisateur du modèle à partir duquel tous les utilisateurs sont copiés, ou comme le prototype utilisateur.
Remarque: Cela n'a pas été testé.
Regardez ici: gérer le rechargement du navigateur socket.io .
Je pense que vous pourriez utiliser la solution que j'ai trouvée. Si vous le modifiez correctement, il devrait fonctionner comme vous le souhaitez.
J'ai regardé ces trucs dernièrement et je pense qu'un chemin différent pourrait être mieux.
Essayez de regarder le bus Azure Service, les ques et le sujet, prenez soin des états hors ligne. Le message attend que l'utilisateur revienne, puis il reçoit le message.
Est un coût pour exécuter une file d'attente, mais c'est comme 0,05 $ par million d'opérations pour une file d'attente de base, de sorte que le coût du développement serait supérieur aux heures de travail nécessaires pour écrire un système de file d'attente. https://Azure.Microsoft.com/en-us/pricing/details/service-bus/
Et le bus Azure a des bibliothèques et des exemples pour PHP, C #, Xarmin, Anjular, Java Script etc.
Le serveur envoie donc un message et n'a pas à se soucier de leur suivi. Le client peut également utiliser un message pour le renvoyer car il peut gérer l'équilibrage de charge si nécessaire.