在forEach循环中调用socket.disconnect实际上并不会在所有套接字上调用断开连接
我是新来的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(socket);});
所以在这种情况下,断开连接事件并不是针对所有套接字触发的。 从arrays中随机抽取了几个套接字。 虽然我能够在队友的帮助下使用setTimeout(fn,0)修复它。
我在线阅读了这篇文章,并且仅仅了解了这一点,setTimeout通过将代码添加到事件队列的末尾来推迟代码的执行。 我读了JavaScript的上下文,调用堆栈,事件循环。 但是我不能把这一切放在一起。 我真的不明白为什么以及如何发生这个问题。 有人能详细解释吗? 什么是解决或避免它们的最好方法。
在gracefulShutdown
如果没有更多关于其他代码的背景知识,我们很难说,但是我很惊讶它会断开所有的套接字:
_socket = _map[ _k ]; socket.disconnect(true);
看起来你是从_map
分配一个项目到variables_socket
,然后调用disconnect
socket
,这是一个不同的variables。 我猜这是一个错字,你打算在_socket
上调用disconnect
?
有些套接字可能由于其他原因断开连接,而且你的环路断开一些但不是所有的套接字的外观可能只是巧合。
据我所知你发布的代码, socket
应该是undefined
,你应该得到错误的尝试调用undefined
的disconnect
方法。
从您使用它的方法名称我可以假设该应用程序在尝试断开所有套接字后退出。 套接字通信的本质是asynchronous的,所以如果你在_map
有相当数量的项目,那么在进程退出之前并不是所有带有disconnect
消息都会被发送。
断开所有sockets后,您可以通过在超时后调用exit
来增加机会。 但是,为什么你会手动断开? 在连接中断远程套接字将自动断开连接…
UPDATE
用于Node.js的Socket.io没有callback来确认发送带有disconnect
命令的数据包。 至less在v0.9。 我已经debugging过,并得出结论,如果不修改源代码,就不可能赶上那一刻。
在文件“socket.io \ lib \ transports \ websocket \ hybi-16.js”中,调用方法write
来发送断开包
WebSocket.prototype.write = function (data) { ... this.socket.write(buf, 'binary'); ... }
然而,在Node.js核心传输“nodejs- {你的节点版本} -src \ core-modules-sources \ lib \ net.js”中将socket.write定义为
Socket.prototype.write = function(chunk, encoding, cb) //cb is a callback to be called on writeRequest complete
然而,正如你所看到的这个callback没有提供,所以socket.io将不知道已经发送的数据包。
在为websocket调用disconnect()
同时,成员disconnected
被设置为true,并且实际上广播“disconnect”事件。 但同步 。 所以.on('disconnect'
服务器套接字上的.on('disconnect'
处理程序不会提供关于数据包是否被发送的有价值的信息。
解
我可以从中得出一个总的结论。 如果确保所有的客户端都被立即通知(而不是等待心跳超时或者如果心跳被禁用),那么这个逻辑应该手动实现。
您可以发送一个普通的消息,这意味着服务器正在closures的客户端,并收到消息后立即呼叫套接字断开。 同时服务器将能够接受所有的确认
服务器端:
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(); }); } }) })
客户应该performance得简单
socket.on('shutdown', function () { socket.disconnect(); });
因此,我们确保每个客户都明确断开连接。 我们不关心服务器。 它很快就会closures。
在示例代码中,它看起来像io.sockets.sockets
是一个对象,但是,至less在我使用的库版本中,它是一个可变数组,每次删除套接字时,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从数组中移除项目 – 因为你们都持有对同一个数组的引用,所以在循环期间修改了数组的内容。
解决方法是在循环之前用slice()创build一个_map的副本:
var _map = io.sockets.sockets.slice(); // copy of the original
它会创build原始数组的副本,因此应该通过数组中的所有项目。
调用setTimeout()
也可以工作的原因是它会推迟删除数组中的项目,从而允许整个循环迭代而不修改套接字-Array。
这里的问题是,sockjs和socket.io使用asynchronous“断开”方法。 IE浏览器。 当您拨打断开连接时,不会立即终止。 这只是一个承诺,将被终止。 这具有以下效果(假设3个套接字)
- 您的for循环抓住第一个套接字
- 断开方法在第一个套接字上调用
- 您的for循环抓住第二个sockets
- 在第二个套接字上调用disconnect方法
- 第一个套接字上的断开方法完成
- 您的for循环抓住第三个套接字
- 在第三个套接字上调用disconnect方法
- 程序自杀
注意,套接字2和3并不一定完成。 这可能是由于许多原因。
最后,正如你所说,setTimeout(fn,0)阻塞了最后的调用,但它可能不一致(我没有深入这个太多)。 我的意思是,你已经把所有的套接字断开连接后设置最终的终止。 setTimeout和setInterval方法实际上更像是一个队列。 您在队列中的位置由您设置的计时器决定。 两个间隔设置为每个10s,他们都同步运行将导致一个运行后另一个。
在Socket.io 1.0之后,库不会向您显示已连接套接字的数组。 你可以检查io.socket.sockets.length,是不是等于打开的套接字对象。 你最好的select是,你向所有你想closures的客户端广播一个“断开连接”的信息,并且在客户端的on.'disconnect'closures实际的WebSocket。