的WebSocket传输可靠性(重新连接期间Socket.io数据丢失)的WebSocket传输可靠

2019-05-13 02:43发布

用过的

的NodeJS,Socket.io

问题

试想一下,有2个用户U1U2,连接到通过Socket.io的应用程序。 该算法如下:

  1. U1完全失去互联网连接(例如,网络交换机关闭)
  2. U2将消息发送到U1。
  3. U1没有收到消息,但因为互联网是下降
  4. 服务器通过心跳超时检测U1断开
  5. U1重新连接到socket.io
  6. U1从来没有从U2收到消息-这是失去了对第4步,我猜。

可能的解释

我想我明白为什么会发生:

  • 第4步服务器杀死Socket实例和消息U1的队列以及
  • 而且在步骤5 U1服务器创建新的连接(这是不重复使用),所以即使邮件仍然是排队,前面的连接反正丢失。

需要帮忙

我怎样才能防止这种数据丢失的? 我必须使用hearbeats,因为我没有人应用挂起,直到永远。 此外,我还必须给出一个可能性重新连接,因为当我部署应用程序的新版本,我想零停机时间。

PS我所说的“消息”的事情不只是一条短信,我可以在数据库中存储,而是宝贵的系统消息,该消息传递必须保证,或最多UI螺丝。

谢谢!


除了1

我已经有一个用户帐户系统。 此外,我的应用程序已经是复杂的。 添加离线/在线状态不会帮助,因为我已经有这种东西。 问题是不同的。

看看第2步在这一步,我们在技术上不能说如果U1下线时 ,他只是失去连接可以说,可能是因为不良互联网的2秒。 所以U2送他的消息,但由于互联网仍然下跌对他(步骤3)U1不会收到它。 需要步骤4,检测脱机用户,可以说,超时时间为60秒。 最终,在U1 10秒钟的互联网连接起来,他重新连接到socket.io。 但是,从U2的消息丢失在太空因为服务器U1被超时断开。

这就是问题所在,我wan't 100%交付。


  1. 收集在{}用户的EMIT(发射名称和数据),通过随机emitID识别。 发送EMIT
  2. 确认客户端上的EMIT(发送发射回服务器emitID)
  3. 如果确认 - 删除{}对象通过emitID鉴定
  4. 如果用户重新连接 - 校验{}为该用户和循环通过它在执行步骤1为每个对象{}
  5. 当断开连接和/或连接的冲洗{}为用户如果有必要

    //服务器常量pendingEmits = {};

    socket.on( '重新连接',()=> resendAllPendingLimits); socket.on( '确认',(emitID)=> {删除(pendingEmits [emitID]);});

    //客户socket.on( '某物',()=> {socket.emit( '确认',emitID);});

Answer 1:

其他人在这个在其他的答案和评论暗示,但根本的问题是,Socket.IO只是一个传递机制,你不能可靠传送仅仅依赖于它。 谁肯定知道消息已被成功地传递到客户端的唯一的人是客户本身 。 对于这种系统,我会建议做如下声明:

  1. 消息不是直接发送到客户端; 相反,他们会发送到服务器和存储在某些类型的数据存储。
  2. 客户有责任问:“我错过了什么”,当他们重新连接,并将查询在数据存储中存储的信息更新他们的状态。
  3. 如果在接收方客户端连接到的信息发送到服务器该消息将被实时发送到客户端。

当然,这取决于应用程序的需求,你可以在这首曲子的片段 - 例如,你可以使用,比如说,一个Redis的列表或分类设置的信息,并清除出来,如果你知道一个事实,一个客户端了至今。


这里有几个例子:

快乐路径

  • U1和U2都连接到该系统。
  • U2将消息发送到U1应该接收服务器。
  • 服务器在某种持久性存储的存储信息,将其标记为与某种时间戳或顺序编号的U1。
  • 服务器经由Socket.IO将消息发送到U1。
  • U1的客户端确认(或许通过Socket.IO回调),它接收到的消息。
  • 服务器删除从数据存储中的持久消息。

离线路径

  • U1失去互联网连接。
  • U2将消息发送到U1应该接收服务器。
  • 服务器在某种持久性存储的存储信息,将其标记为与某种时间戳或顺序编号的U1。
  • 服务器经由Socket.IO将消息发送到U1。
  • U1的客户端确认收货,因为它们是离线。
  • 也许U2 U1发送几个消息; 他们都得到存储在以同样的方式将数据存储。
  • 当U1重新连接,它要求服务器“的最后一条消息我看到的是X /我有状态X,我错过了什么。”
  • 服务器发送U1的所有它的数据存储错过基于U1的请求消息
  • U1的客户端确认收到和服务器中删除从数据存储中的消息。

如果你绝对要保证交付,然后它来设计你的系统,这样的方式连接,实际上并不事情是很重要的,而且实时传送是一个简单的奖金 ; 这几乎总是涉及某种形式的数据存储。 作为user568109在注释提到的,有邮件系统抽象远的存储和递送所述消息,并且它可以是值得探讨这样的预建溶液。 (你可能还是要自己编写Socket.IO整合。)

如果你不感兴趣,在数据库中存储的信息,您可以逃脱它们存储在本地阵列; 服务器尝试发送U1的消息,并将其存储在“等待消息”的列表,直到U1的客户确认其收到。 如果客户端处于脱机状态,那么当它回来了它可以告诉服务器“嘿,我被中断,请给我什么,我错过了”,而服务器可以通过这些信息进行迭代。

幸运的是,Socket.IO提供了一种机制,允许客户端为“响应”,看起来像本地JS回调的消息。 下面是一些伪代码:

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

这是颇为相似,最后建议,根本没有持久性数据存储。


您还可能有兴趣在概念事件采购 。



Answer 2:

它似乎是你已经拥有的用户帐户系统。 你知道哪一个账户在线/离线,您可以处理连接/断开事件:

因此,解决办法是,增加在线/离线和数据库中的每个用户离线消息:

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


Answer 3:

看看这里: 处理浏览器重装socket.io 。

我想你可以使用我想出了解决方案。 如果你正确地修改它,它应该工作,只要你想。



Answer 4:

我想你想要的是为每个用户可重复使用的插座,这样的:

客户:

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

服务器:

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

然后套接字队列断开之间保留。

请确保它的每个用户节省socket属性数据库,并让你的用户原型的方法的一部分。 该数据库没有关系,只是保存它,但是你已经保存的用户。

这将通过作为发送标记消息之前需要来自客户端的确认避免在additon 1中提到的问题。 如果你真的想,你可以给每个消息的ID,并在客户端发送消息ID msg-conf ,然后检查。

在这个例子中, user是模板的用户,所有用户都从,或喜欢的用户原型复制。

注意:这并没有经过测试。



Answer 5:

一直在研究这个东西近来,认为不同的路径可能会更好。

试着看一下Azure的服务总线,疑问句和话题照顾离线状态。 消息等待用户回来,然后他们得到的消息。

是运行队列中,但每百万次操作的一个基本的队列它像$ 0.05成本,使开发成本将是从几个小时的工作更需要写一个排队系统。 https://azure.microsoft.com/en-us/pricing/details/service-bus/

和天蓝色巴士有PHP,C#,Xarmin,Anjular,Java脚本等库和示例

因此,服务器发送消息,并不需要担心他们的跟踪。 客户端可以使用消息发回也仿佛必要手段可以处理负载平衡。



Answer 6:

试试这个EMIT聊天列表

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



文章来源: Websocket transport reliability (Socket.io data loss during reconnection)