Подтвердить что ты не робот

Вызов socket.disconnect в цикле forEach фактически не вызывает разъединение во всех сокетах

Я новичок в javascript мире. Недавно я работал над чат-приложением в nodejs. Поэтому у меня есть метод, называемый gracefulshutdown следующим образом.

var gracefulShutdown = function() {
    logger.info("Received kill signal, shutting down gracefully.");
    server.close();
    logger.info('Disconnecting all the socket.io clients');
    if (Object.keys(io.sockets.sockets).length == 0) process.exit();
    var _map = io.sockets.sockets,
        _socket;
    for (var _k in _map) {
        if (_map.hasOwnProperty(_k)) {
            _socket = _map[_k];
            _socket.disconnect(true);
        }
    }
    ...code here...
    setTimeout(function() {
        logger.error("Could not close connections in time, shutting down");
        process.exit();
    }, 10 * 1000);
}

Вот что происходит в отключенном прослушителе. Метод removeDisconnectedClient просто обновляет запись в db, чтобы указать удаленный клиент.

socket.on('disconnect', function() {   removeDisconnectedClient (розетка); });

Итак, в этом случае событие разъединения не было запущено для всех сокетов. Он был запущен только из нескольких сокетов из массива. Хотя я смог исправить это с помощью setTimeout (fn, 0) с помощью товарища по команде.

Я прочитал об этом в Интернете и понял только то, что setTimeout отменил выполнение кода, добавив его в конец очереди событий. Я читал о контексте javascript, стекх вызовов, цикле событий. Но я не мог собрать все это в этом контексте. Я действительно не понимаю, почему и как эта проблема возникла. Может кто-нибудь объяснить это подробно. И каков наилучший способ их решить или избежать.

4b9b3361

Ответ 1

Трудно сказать без лишнего контекста о остальной части кода в gracefulShutdown, но я удивлен, что он отключает любой из сокетов:

_socket = _map[ _k ];
socket.disconnect(true);

Кажется, что вы назначаете элемент из _map переменной _socket, а затем вызываете disconnect на socket, что является другой переменной. Я предполагаю, что это опечатка, и вы хотели называть disconnect на _socket?

Некоторые из сокетов могут быть отключены по другим причинам, и появление того, что ваш цикл отключает некоторые, но не все сокеты, вероятно, просто совпадение.

Насколько я могу судить по указанному вами коду, socket должен быть undefined, и вы должны получать ошибки о попытке вызвать метод disconnect на undefined.

Ответ 2

Из имени метода, в котором вы его используете, я могу предположить, что это приложение выходит после попытки отключить все сокеты. Характер связи сокета является асинхронным, поэтому, если у вас есть приличное количество элементов в _map, может случиться, что не все сообщения с disconnect будут отправлены до выхода процесса.

Вы можете увеличить шансы, вызвав exit после некоторого таймаута после отключения всех сокетов. Однако почему вы вручную отключились? При прерывании соединения удаленные сокеты автоматически отсоединяются...

ОБНОВЛЕНИЕ
Socket.io для Node.js не имеет обратного вызова, чтобы точно знать, что пакет с командой disconnect был отправлен. По крайней мере, в v0.9. Я отлаживал это и пришел к выводу, что без изменения источников невозможно поймать этот момент.

В файле "socket.io\lib\transports\websocket\hybi-16.js" вызывается метод write для отправки пакета разъединения

WebSocket.prototype.write = function (data) {
...
   this.socket.write(buf, 'binary');
...
}

В то время как socket.write определяется в Node.js core transport "nodejs- {your- node -version} -src\core-modules-sources\lib\net.js" как

Socket.prototype.write = function(chunk, encoding, cb)
//cb is a callback to be called on writeRequest complete

Однако, как вы видите, этот обратный вызов не предоставляется, поэтому socket.io не будет знать о пакете, который был отправлен.

В то же время, когда disconnect() вызывается для websocket, член disconnected устанавливается равным true, и, действительно, транслируется событие "отключить". Но синхронно. Поэтому обработчик .on('disconnect' на сокете сервера не дает и ценную информацию о том, был ли отправлен пакет или нет.

Решение
Я могу сделать общий вывод из этого. Если это так важно, чтобы убедиться, что все клиенты сразу информированы (и не дожидаться таймаута пульса или если heartbeat отключен), тогда эта логика должна быть выполнена вручную.

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

на стороне сервера:

var sockets = [];
for (var _k in _map) {
    if (_map.hasOwnProperty(_k)) {
        sockets.push(_map[_k]);
    }
}
sockets.map(function (socket) {
    socket.emit('shutdown', function () {
        socket.isShutdown = true;
        var all = sockets.every(function (skt) {
            return skt.isShutdown;
        });
        if (all) {
            //wrap in timeout to let current tick finish before quitting
            setTimeout(function () {
                process.exit();
            });
        }
    })
})

Клиенты должны вести себя просто

socket.on('shutdown', function () {
    socket.disconnect();
});

Таким образом, мы гарантируем, что каждый клиент явно отключен. Мы не заботимся о сервере. Он будет закрыт в ближайшее время.

Ответ 3

В примере кода он выглядит как io.sockets.sockets - это Object, однако, по крайней мере, в используемой мне библиотечной версии, это изменчивый массив, который библиотека socket.io может изменять каждый раз, когда вы удаляете сокет с disconnect(true).

Таким образом, когда вы вызываете disconnect(true);, если текущий итерационный элемент из индекса i удаляется, этот эффект происходит следующим образом:

var a = [1,2,3,4];
for( var i in a) {
   a.splice(i,1); // remove item from array
   alert(i);
}
// alerts 0,1 

Таким образом, вызов disconnect (true) попросит socket.io удалить элемент из массива - и поскольку вы оба сохраняете ссылку на один и тот же массив, содержимое массива изменяется во время цикла.

Решение должно создать копию _map с slice() перед циклом:

var _map = io.sockets.sockets.slice(); // copy of the original

Он создаст копию исходного массива и, следовательно, должен пройти все элементы в массиве.

Причина, по которой вызов setTimeout() также будет работать, заключается в том, что он отложит удаление элементов из массива, позволяя повторять весь цикл без изменения сокетов -Array.

Ответ 4

Проблема заключается в том, что sockjs и socket.io используют асинхронные методы "отключить". IE. Когда вы вызываете разъединение, он не сразу прекращается. Это просто обещание, что оно будет прекращено. Это имеет следующий эффект (предполагая 3 сокета)

  • Ваш цикл for захватывает первый сокет
  • Метод разъединения вызывается в первом сокете
  • Ваш цикл for захватывает второй сокет
  • Метод разъединения вызывается во втором сокете
  • Метод разъединения на первом сокете заканчивается
  • Ваш цикл for захватывает третий сокет
  • Метод разъединения вызывается в третьем сокете
  • Программа убивает себя

Обратите внимание, что разъемы 2 и 3 еще не завершены. Это может быть по ряду причин.

Наконец, setTimeout (fn, 0), как вы сказали, блокирует окончательный вызов, но может быть непротиворечивым (я не слишком вникнул в это). Под этим я подразумеваю, что вы установили окончательное завершение после того, как все ваши сокеты отключились. Методы setTimeout и setInterval по существу действуют скорее как очередь. Ваша позиция в очереди определяется заданным вами таймером. Два интервала, заданные для 10 секунд каждый, где они оба запускают синхронно, заставят один запускать ПОСЛЕ другого.

Ответ 5

После Socket.io 1.0 библиотека не предоставляет вам массив подключенных сокетов. Вы можете проверить, что io.socket.sockets.length не равно открытым объектам сокета. Лучше всего, чтобы вы передавали сообщение об отключении всем клиентам, которых вы хотите отключить, а on.'disconnect 'на стороне клиента закрывают фактический WebSocket.