Надежность транспорта Websocket (Socket.io потеря данных при повторном подключении)



использовать



NodeJS, Socket.io





представьте, что есть 2 пользователя U1 & U2, подключенный к приложению через Socket.io. алгоритм следующий:





  1. U1 полностью теряет подключение к интернету (исх. выключает интернет)


  2. U2 отправить сообщение U1.


  3. U1 пока не получает сообщение, потому что Интернет не работает


  4. сервер обнаруживает U1 отключение по таймауту сердцебиения


  5. U1 повторное подключение к socket.io


  6. U1 никогда не получает сообщение от U2 - он потерян на шаге 4, я думаю.


возможное объяснение



Я думаю, что понимаю, почему это происходит:




  • Шаг 4 сервер убивает экземпляр сокета и очередь сообщений на U1 а также

  • кроме того, на шаге 5 U1 и сервер создать новое соединение (оно не используется повторно), поэтому даже если сообщение все еще находится в очереди, предыдущее соединение все равно теряется.


нужна помощь



Как я могу предотвратить такую потерю данных? Я должен использовать hearbeats, потому что я не люди висят в приложении навсегда. Также я должен еще дать возможность переподключиться, потому что при развертывании нового версия приложения я хочу нулевое время простоя.





спасибо!





Дополнение 1



у меня уже есть система учетных записей пользователей. Более того, мое приложение уже сложное. Добавление оффлайн / онлайн статусов не поможет, потому что у меня уже есть такие вещи. Проблема в другом.



Шаг 2. На этом этапе мы технически Не могу сказать, если U1 переходит в автономный режим, он просто теряет соединение, скажем, на 2 секунды, вероятно, из-за плохого интернета. Поэтому U2 отправляет ему сообщение, но U1 не получает его, потому что интернет все еще не работает для него (Шаг 3). Шаг 4 необходим для обнаружения автономных пользователей, скажем, тайм-аут составляет 60 секунд. В конце концов, еще через 10 секунд подключение к интернету для U1 будет восстановлено, и он снова подключится к socket.io но сообщение от U2 теряется в пространстве, потому что на сервере U1 был отключен по таймауту.



вот в чем проблема, я не хочу 100% доставки.





решение




  1. соберите эмиссию (имя и данные эмиссии) в {} Пользователь, идентифицированный случайным эмиссией. Отправить эмиссию

  2. подтвердите эмиссию на стороне клиента (отправьте эмиссию обратно на сервер с помощью emitID)

  3. если подтверждено-удалить объект из {}, идентифицированный emitID

  4. если пользователь снова подключен-проверьте {} для этот пользователь и цикл через него выполнение шага 1 для каждого объекта в {}


  5. при отключении или / и подключении flush {} для пользователя при необходимости



    // сервер
    const pendingEmits = {};



    гнездо.on ('reconnection', () = > resendAllPendingLimits);
    разъем.on ('confirm', (emitID) = > { delete(pendingEmits[emitID]);});



    // клиент
    разъем.on ('something', () => {
    разъем.emit ('confirm', emitID);
    });



632   5  

5 ответов:

другие намекали на это в других ответах и комментариях, но основная проблема заключается в том, что Socket.IO это просто механизм доставки, а вы не может зависите от его самостоятельно для надежной поставки. Единственный человек, который точно знает, что сообщение было успешно доставлено клиенту сам клиент. Для такого рода системы я бы рекомендовал сделать следующие утверждения:

  1. сообщения не отправляются непосредственно клиентам; вместо этого, они отправляются на сервер и хранится в каком-то хранилище данных.
  2. клиенты отвечают за запрос "что я пропустил" при повторном подключении и будут запрашивать сохраненные сообщения в хранилище данных для обновления их состояния.
  3. если сообщение отправлено на сервер пока клиент получателя подключен, это сообщение будет отправлено в режиме реального времени клиенту.

конечно, в зависимости от потребностей вашего приложения, вы можете настроить кусочки например, вы можете использовать, скажем, список Redis или сортированный набор для сообщений и очистить их, если вы знаете, что клиент обновлен.


вот несколько примеров:

счастливого пути:

  • U1 и U2 оба подключены к системе.
  • U2 отправляет сообщение на сервер, который должен получить U1.
  • сервер хранит сообщение в каком-то постоянном хранилище, помечая его для U1 с помощью какая-то временная метка или последовательный идентификатор.
  • сервер отправляет сообщение на U1 через Socket.IO.
  • клиент U1 подтверждает (возможно, через a Socket.IO обратный вызов), чтобы он получил сообщение.
  • сервер удаляет сохраненное сообщение из хранилища данных.

автономный путь:

  • U1 теряет подключение к интернету.
  • U2 отправляет сообщение на сервер, который должен получить U1.
  • в сервер хранит сообщение в каком-то постоянном хранилище, помечая его для U1 какой-то временной меткой или последовательным идентификатором.
  • сервер отправляет сообщение на U1 через Socket.IO.
  • клиент 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
});

Это очень похоже на последнее предложение, просто без постоянного хранилища данных.


вас также может заинтересовать понятие событие поиска.

похоже, что у вас уже есть система учетных записей пользователей. Вы знаете, какая учетная запись находится в сети / в автономном режиме, вы можете обрабатывать события подключения / отключения:

таким образом, решение заключается в добавлении онлайн / оффлайн и оффлайн сообщений в базу данных для каждого пользователя:

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

смотрите здесь: обрабатывать перезагрузку браузера socket.io.

Я думаю, вы могли бы использовать решение, которое я придумал. Если вы измените его правильно, он должен работать как вы хотите.

Я думаю, что вы хотите иметь многоразовый сокет для каждого пользователя, что-то вроде:

клиент:

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 свойство базы данных и сделать методы частью вашего прототипа пользователя. База данных не имеет значения, просто сохраните ее, однако вы сохраняете своих пользователей.

Это позволит избежать в дополнение 1, требуя подтверждения от клиента, прежде чем пометить сообщение как отправленное. Если вы действительно хотите, вы можете дать каждому сообщению идентификатор и заставить клиента отправить идентификатор сообщения в msg-conf, а затем проверить его.

в этом примере user - это пользователь шаблона, из которого копируются все пользователи, или как прототип пользователя.

Примечание: это не было проверено.

смотрю на эти вещи в последнее время и думаю, что другой путь может быть лучше.

попробуйте посмотреть на служебную шину Azure, вопросы и темы позаботьтесь о состоянии off line. Сообщение подождите, пока пользователь вернется, а затем они получат сообщение.

Это стоимость запуска очереди, но ее как $ 0,05 за миллион операций для базовой очереди, поэтому стоимость dev будет больше от часов работы нужно написать очередь система. https://azure.microsoft.com/en-us/pricing/details/service-bus/

и azure bus имеет библиотеки и примеры для PHP, C#, Xarmin, Anjular, Java Script и т. д.

таким образом, сервер отправляет сообщение и не нужно беспокоиться об их отслеживании. Клиент может использовать сообщение для отправки обратно также как средство может обрабатывать балансировку нагрузки, если это необходимо.

Comments

    Ничего не найдено.