NodeJS函数被socketio事件中断

我在看似并发的nodejs游戏服务器中看到了一些奇怪的行为。 这很奇怪,因为Nodejs应该在一个线程中运行,因为它不使用任何并发。 问题是我有一个使用setImmediate()重复调用的更新函数。 在这个函数中,我在两个地方使用了一个数组。 但是,当“disconnect”事件触发时(也就是客户端与服务器断开连接时),同样的数组也会被修改。 所以恰巧在时序alignment的情况下,断开事件在更新函数中访问数组的第一个位置触发,但在第二个位置之前触发,数组被修改,所以服务器在尝试数组时尝试被访问在第二位。

下面是一些可能使这张图片清晰的代码:

function update(){ for(var i = 0; i < gameWorlds.length; i++){ gameWorlds[i].update(); console.log("GAMEWORLDS LENGTH BEFORE: " + gameWorlds.length); NetworkManager.sendToClient(gameWorlds[i].id, "gameupdate", gameWorlds[i].getState()); console.log("GAMEWORLDS LENGTH AFTER: " + gameWorlds.length); gameWorlds[i].clearGameState(); } } setImmediate(update); //in the NetworkManager module, the disconnect event handler: socket.on("disconnect", function(){ for(var a = 0; a < sockets.length; a++){ if(sockets[a].id === socket.id){ sockets.splice(a, 1); } } listenerFunction("disconnect", socket.id); console.log("Client " + socket.id + " DISCONNECTED!"); }); //also in the NetworkManager module, the sendToClient function: function sendToClient(clientId, messageName, data){ for(var i = 0; i < sockets.length; i++){ if(sockets[i].id === clientId){ sockets[i].emit(messageName, data); } } } //in the main module (the same one as the update function), the listener //function that's called in the disconnect event handler: function networkEventsListener(eventType, eventObject){ if(eventType === "disconnect"){ for(var i = 0; i < gameWorlds.length; i++){ if(gameWorlds[i].id === eventObject){ gameWorlds.splice(i, 1); console.log("GAME WORLD DELETED"); } } } } 

现在,我有一个socketio事件监听器,当客户端断开连接时,数组中的一个元素被删除。 当这个事件发生在第一和第二个地方之间的数组访问(如上所示),我的服务器崩溃。 无论是使用线程还是我的函数停止让事件处理程序执行,然后我的function恢复。 无论哪种方式,我不希望这种情况发生。 谢谢!

编辑1:我编辑了代码,以合并我在我的代码中的控制台日志。 我之所以说我的循环被中断是因为第二个控制台日志输出长度为0,而第一个控制台日志输出大于0的事实。另外,在断开事件处理程序中还有另一个控制台日志在两个控制台之间的防火墙login我的更新function。 这意味着我的function正在中断。

编辑2:谢谢你所有的答复,我真的很感激它。 我认为在以下方面存在一些困惑:1.没有人知道控制台日志是如何出现的。 在我之前的编辑中,我更改了代码以反映我如何logging以查看问题。 问题是在断开连接事件处理程序中,我有一个控制台日志,发生在循环中的两个控制台日志之间。 即断开事件处理程序在循环中到达第二个控制台日志之前执行。 除非我对控制台日志function的实现感到困惑,否则日志应该以正确的顺序发生(即循环中的两个控制台日志应该始终在其他任何控制台login到程序其余部分之前ASYNC性质,因为你们大多数人已经说过)。但事实并非如此,这导致我相信一些奇怪的事情正在发生。 2.循环内没有任何代码正在改变数组。 在很多你的回复中,你认为有一些代码实际上修改了循环内部的数组,但事实并非如此。 唯一修改数组的代码是循环的代码OUTISDE,这就是为什么非常奇怪的是访问数组的循环的第一部分不会崩溃,但是第二部分不会崩溃,即使DOESN改变arrays。

编辑3:好,所以很多的答复已经要求完整的代码。 我已经用所有相关的REAL代码更新了代码。

node.js中的Javascript是单线程的。 在Javascript中执行给定的线程不会被socket.io断开事件中断。 那身体不可能发生。 node.js是事件驱动的。 当断开事件发生时,一个事件将被放入到Javascript事件队列中,并且只有当你当前的执行线程完成时,Javascript才会将下一个事件从事件队列中取出,并调用与之关联的callback。

你没有足够的真实的代码来确定,但是如果你有asynchronous操作,那么当你开始一个asynchronous操作并为它的完成注册一个callback的时候,你会完成那个Javascript线程执行,这只是一场竞赛,看看哪个asynchronous事件接下来发生(完成这个特定的asynchronous操作或从socket.io断开连接的断开事件)。 这是不确定的,这些事件可以以任何顺序发生。 因此,如果在代码中有asynchronous代码,则可以在该代码正在等待asynchronous事件完成时处理断开连接事件。


这是您在node.js编程中必须注意的竞争条件types。 任何时候,当你的逻辑变成asynchronous时,你的代码正在等待exception的callback函数,这个函数可以在node.js中得到处理。

究竟要做什么完全取决于具体情况,我们需要查看和理解您的真实代码(而不是伪代码),以了解哪个选项最适合您。 仅供参考,如果您向我们展示您的真实代码,而不仅仅是伪代码,这也是我们能够更好地帮助您的原因之一。

以下是可以在共享数据结构上操作asynchronous操作时使用的一些技术,这些操作可以通过其他asynchronous代码进行更改:

  1. 复制要处理的数据,使其他代码无法访问您的副本,因此无法使用其他代码进行修改。 这可能是制作一个数组的副本,或者可能只是使用闭包来在本地捕获索引,因此索引不会受到其他代码的影响。

  2. 使用标志来保护正在修改中的数据结构,并训练所有其他代码以尊重该标志。 究竟如何做到这一点取决于具体的数据。 我有一个Raspberry Pi node.js应用程序中的代码,它定期将数据保存到磁盘,并受到其他事件驱动代码可能需要更新数据的竞争条件,而我正在使用asynchronousI / O写入它到磁盘。 由于数据可能很大,而且系统的内存也不是很大,所以我不能像第一点中所build议的那样复制数据。 所以,我使用了一个标志来表明我正在将数据写入磁盘,而任何希望在设置此标志时修改数据的代码将其操作添加到队列中,而不是直接修改数据。 然后,当我完成将数据写入磁盘时,代码会检查队列以查看是否需要执行任何挂起的操作来修改数据。 而且,由于数据是由一个对象来表示的,并且数据上的所有操作都是通过对象上的方法来执行的,所以这些对于使用数据的代码来说都是透明的或者试图修改数据。

  3. 将数据放入一个实际的数据库中,该数据库内置有并发function和控件,以便可以对数据进行primefaces更改,或者可以在短时间内locking数据,或者可以安全地获取或更新数据。 数据库有很多可能的策略来处理这个问题,因为它们发生在他们身上。

  4. 使所有对数据的访问都是asynchronous的,所以如果其他一些asynchronous操作正在修改数据,那么其他不安全的访问数据的操作就会“阻塞”,直到原来的操作完成。 这是数据库使用的一种技术。 当然,您必须注意死锁或标志或locking未被清除的错误path。


根据您发布的更多代码提出一些新的评论:

这段代码是错误的:

 //in the main module (the same one as the update function), the listener //function that's called in the disconnect event handler: function networkEventsListener(eventType, eventObject){ if(eventType === "disconnect"){ for(var i = 0; i < gameWorlds.length; i++){ if(gameWorlds[i].id === eventObject){ gameWorlds.splice(i, 1); console.log("GAME WORLD DELETED"); } } } } 

在迭代的数组的for循环中调用.splice()时,会导致错过数组中正在迭代的项目。 我不知道这是否与你的问题有关,但这是错误的。 一个简单的方法来避免这个问题,它向后迭代数组。 然后调用.splice()不会影响任何尚未迭代的数组元素的位置,也不会错过数组中的任何内容。

在您的disconnect处理程序for循环中同样的问题。 如果你只希望在你的迭代中有一个数组元素匹配,那么你可以在splice()之后break ,这样可以避免这个问题,你不需要向后迭代。

我认为你应该改变两件事来解决问题。

1)在断开连接时不要修改数组的长度,而是设置一个falsey的值。 一个布尔值或一个和零的情况

2)以if语句的forms添加逻辑来检查玩家二的值是否为false。 这样,你就会知道他们断开了,不配有任何东西,因为他们是跛脚的,不能看失败者的屏幕。

这应该解决这个问题,你可以。 决定如果他们懒惰留下并观看你的游戏的胜利失败仪式该怎么办。

var gameWorld = [];

function update(){ // some code }是asynchronous的,并被推送到事件循环。 function disconnect(){ // some code }也是asynchronous的并被推送到事件循环。

尽pipeupdate()在调用堆栈上运行,但它正在等待事件循环,并不意味着它会在下一次打勾前完成执行。 gameWorld不在两个范围之内,都可以在update()中间修改。 所以当update()试图再次访问数组时,它就不一样了。

在update()完成并修改事件循环nexttick()的数组之前调用disconnect(),直到update()的代码到达第二个玩家bam时,数组会被搞乱。

即使你有一个事件监听器,执行不应该停止中间函数。 事件发生时,节点会将事件callback推入堆栈。 然后,当节点完成执行当前函数时,它将开始处理堆栈上的其他请求。 你不能确定事情的执行顺序,但是你可以肯定事情在执行中不会中断。

如果你的doWhatever函数是asynchronous的,那么问题可能会发生,因为当节点最终到达服务堆栈上的请求时,循环已经完成,因此无论什么时候调用它都被调用相同的索引(无论它的最后一个值是什么。)

如果你想从一个循环调用asynchronous函数,那么你应该把它们包装在一个函数中来保存参数。

例如

 function doWhateverWrapper(index){ theArray[index].doWhatever(); } function update(){ for(var i = 0; i < theArray.length; i++){ //first place the array is accessed doWhateverWrapper(i); ....more code..... //second place the array is accessed doWhateverWrapper(i); } } setImmediate(update);