在forEach循环中调用socket.disconnect并不能实际调用所有套接字上的disconnect

时间:2015-06-18 19:18:20

标签: javascript node.js sockets

我是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方法只是更新数据库中的条目以指示已删除的客户端。

socket.on(' disconnect',function(){     removeDisconnectedClient(插座); });

因此,在这种情况下,并未针对所有套接字触发断开连接事件。它是从阵列中随机抽出的几个套接字。虽然我能够在队友的帮助下使用setTimeout(fn,0)修复它。

我在网上读到它并且只了解setTimeout通过将代码添加到事件队列的末尾来推迟代码的执行。我读了关于javascript上下文,调用堆栈,事件循环。但在这种情况下,我无法将所有这些内容整合在一起。我真的不明白为什么以及如何发生这个问题。有人可以详细解释一下。什么是解决或避免它们的最佳方法。

5 个答案:

答案 0 :(得分:4)

如果没有关于gracefulShutdown中其余代码的更多上下文,很难肯定地说,但我很惊讶它完全断开了任何套接字:

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

您似乎正在将_map中的项目分配给变量_socket,然后在disconnect上调用socket,这是一个不同的变量。我猜这是一个错字,你打算在disconnect上致电_socket

由于其他原因,某些套接字可能正在断开连接,并且您的循环断开某些但不是所有套接字的外观可能只是巧合。

据我所知,您发布的代码socket应为undefined,并且您在尝试调用disconnect undefined方法时遇到错误

答案 1 :(得分:3)

从您使用它的方法名称我可以假设应用程序在尝试断开所有套接字后退出。套接字通信的本质是异步的,因此,如果_map中有大量项目,则可能会发生并非所有带有disconnect的消息都会在进程退出之前发送。

断开所有套接字后,在超时后调用exit可以增加机会。但是,为什么要手动断开连接?在连接中断时,远程套接字将自动断开连接......

更新
Node.js的Socket.io没有回调来确定已发送带有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核心传输中定义,而节点js- {你的节点版本} -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将不知道已发送的数据包。

在为websocket调用disconnect()的同时,成员disconnected设置为true,并且"断开"事实是广播的。但同步。因此,服务器套接字上的.on('disconnect'处理程序不会提供有关数据包是否已发送的有价值信息。

<强>解决方案
我可以从中得出一般性的结论。如果确保立即通知所有客户端(而不是等待心跳超时或禁用心跳)非常重要,那么应该手动实现此逻辑。

您可以发送一条普通邮件,这意味着客户端服务器正在关闭,并在收到邮件后立即调用套接字断开连接。同时服务器将能够接受所有确认

服务器端:

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

因此,我们确保每个客户端已明确断开连接。我们不关心服务器。它很快就会关闭。

答案 2 :(得分:2)

在示例代码中看起来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从数组中删除该项 - 并且因为您都持有对同一数组的引用,所以在循环期间修改了数组的内容。 p>

解决方案是在循环之前用slice()创建_map的副本:

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

它会创建原始数组的副本,因此应该遍历数组中的所有项目。

调用setTimeout()也可以工作的原因是它会推迟从数组中删除项目,允许整个循环迭代而不修改套接字-Array。

答案 3 :(得分:1)

这里的问题是sockjs和socket.io使用异步“disconnect”方法。 IE浏览器。当您调用disconnect时,它不会立即终止。它只是一个承诺它将被终止。这具有以下效果(假设3个插座)

  1. 你的for循环抓住第一个插座
  2. 在第一个插槽上调用disconnect方法
  3. 你的for循环抓住第二个插座
  4. 在第二个插槽上调用disconnect方法
  5. 第一个插槽上的断开连接方法完成
  6. 你的for循环抓住第三个插座
  7. 在第三个插槽上调用disconnect方法
  8. 程序自杀
  9. 请注意,套接字2和3尚未完成。这可能有很多原因。

    最后,正如你所说的那样,setTimeout(fn,0)阻止了最后的调用,但它可能不一致(我没有过多地挖掘它)。我的意思是,你已将最终终止设置为所有套接字断开后。 setTimeout和setInterval方法本质上更像是一个队列。您在队列中的位置由您设置的计时器决定。两个间隔设置为10s,其中两个间隔同步运行将导致一个在另一个之后运行。

答案 4 :(得分:1)

在Socket.io 1.0之后,库不会向您显示已连接套接字的数组。您可以检查io.socket.sockets.length是否等于打开的套接字对象。您最好的选择是向所有要关闭的客户端广播“断开连接”消息,并在客户端on.'disconnect'关闭实际的WebSocket。