【问题标题】:Socket.io 1.3.7 not cleaning up on client disconnectSocket.io 1.3.7 在客户端断开连接时未清理
【发布时间】:2016-03-11 06:12:45
【问题描述】:

我有一个 node.js 脚本,它允许客户端连接并从外部脚本接收一些实时数据。

我刚刚将 node.js 和 socket.io 升级到当前版本(从

这是我当前的 node.js 脚本;

var options = {
    allowUpgrades: true,
    pingTimeout: 50000,
    pingInterval: 25000,
    cookie: 'k1'
    };

var io = require('socket.io')(8002, options);
cp = require('child_process');
var tail = cp.spawn('test-scripts/k1.rb');


//On connection do the code below//
io.on('connection', function(socket) {
    console.log('************ new client connected ****************', io.engine.clientsCount);

    //Read from mongodb//
    var connection_string = '127.0.0.1:27017/k1-test';
    var mongojs = require('mongojs');
    var db = mongojs(connection_string, ['k1']);
    var k1 = db.collection('k1');
    db.k1.find({}, {'_id': 0, "data.time":0}).forEach(function(err, doc) {
        if (err) throw err;
        if (doc) { socket.emit('k1', doc); }
        });

    //Run Ruby script & Listen to STDOUT//
    tail.stdout.on('data', function(chunk) {
        var closer = chunk.toString()
        var sampArray = closer.split('\n');
        for (var i = 0; i < sampArray.length; i++) {
        try {
            var newObj = JSON.parse(sampArray[i]);
            // DO SOCKET //
            socket.emit('k1', newObj);
            } catch (err) {}
        }   
    });

    socket.on('disconnect', function(){
    console.log('****************** user disconnected *******************', socket.id, io.engine.clientsCount);
    socket.disconnect();
    });

});

在旧版本的 socket.io 中,当客户端退出时,我得到以下登录调试;

   info  - transport end (undefined)
   debug - set close timeout for client Owb_B6I0ZEIXf6vOF_b-
   debug - cleared close timeout for client Owb_B6I0ZEIXf6vOF_b-
   debug - cleared heartbeat interval for client Owb_B6I0ZEIXf6vOF_b-
   debug - discarding transport

然后一切顺利,一切都很好。

当客户端退出时,使用新 (1.3.7) 版本的 socket.io,我得到以下登录调试信息;

  socket.io:client client close with reason transport close +2s
  socket.io:socket closing socket - reason transport close +1ms
  socket.io:client ignoring remove for -0BK2XTmK98svWTNAAAA +1ms
****************** user disconnected ******************* -0BK2XTmK98svWTNAAAA

注意socket.io:client ignoring remove for -0BK2XTmK98svWTNAAAA这一行

但在那之后并且没有其他客户端连接到服务器,我仍然看到它试图将数据写入已经离开的客户端。 (在下面的示例中,这是我连接了 2 个客户端后得到的结果,这两个客户端都已断开连接。

  socket.io:client ignoring packet write {"type":2,"data":["k1",{"item":"switch2","datapoint":{"type":"SWITCH","state":"0"}}],"nsp":"/"} +1ms
  socket.io:client ignoring packet write {"type":2,"data":["k1",{"item":"switch2","datapoint":{"type":"SWITCH","state":"0"}}],"nsp":"/"} +3ms

我正在尝试阻止这种明显的新行为,以便一旦客户端断开连接并且服务器处于空闲状态,它就不会继续尝试发送数据。

我一直在玩 socket.disconnectdelete socket["id"],但我还是一样。

我尝试使用 io.close() 哪种方法有效 - 它启动了任何实际连接的客户端并让它们重新连接,但仍然让服务器坐在那里试图向已离开的客户端发送更新。

我是否遗漏了一些明显的东西,或者新版本的 socket.io 的处理方式发生了变化? migration doc 中没有关于此的内容。我发现的唯一其他结果是 2014 年 6 月的 this 错误报告,已标记为已关闭。从我的阅读来看 - 这似乎与我遇到的问题相同,但使用当前版本。

更新:我进行了更多测试,并将io.engine.clientsCount 添加到console.log 的两个实例中以跟踪它在做什么。当我连接 1 个客户端时,它会出现 1(如预期的那样),当我关闭该客户端时,它会变为 0(如预期的那样),这让我相信客户端连接已关闭并且 engine.io 知道这一点。那么为什么我仍然会看到所有“忽略数据包写入”行以及每个已断开连接的客户端的更多信息。

更新 2: 我已经更新了上面的代码以包含解析器部分和 DB 部分 - 这代表了完整的节点脚本,因为我认为我可能需要清理自己的客户。我已经尝试将以下代码添加到脚本中,希望它会但可惜不是:(

在连接事件中我添加了clients[socket.id] = socket;,在断开连接事件中我添加了delete clients[socket.id];,但它并没有改变任何东西(我可以看到)

更新 3: 感谢@robertklep,这是我真正在寻找的“事件处理程序泄漏”。发现后我也找到了this的帖子。

【问题讨论】:

  • 看起来您自己的代码一直在写入已经关闭的套接字(您没有表明一旦客户端断开连接就清理数据库连接和 Ruby 进程)。
  • Ahhh - 这听起来很有趣 - 稍后我会用完整的代码更新问题,但我的旧节点/套接字 0.9 应用程序中从未有任何东西通过关闭的方式下。如果您有时间在格林威治标准时间 19:00 之后回来查看,我将更新代码中缺失的部分。可能需要一些关于如何进行清理的指示。
  • 我的猜测是您之前的代码表现出相同的行为,但 socket.io 只是没有记录任何内容 ;-)
  • @robertklep - 如果您不介意将目光投向它,请使用完整的节点脚本更新上面的代码。这(显然)在“旧节点”中工作正常如果您想查看正在被引用的 ruby​​ 文件,这可能会有点困难但并非不可能。

标签: node.js socket.io socket.io-1.0


【解决方案1】:

我的猜测是,较新的 socket.io 只是向您展示(通过调试消息)旧 socket.io 中已经发生的情况,只是没有被记录。

我认为主要问题是这个设置:

var tail = cp.spawn('test-scripts/k1.rb');

io.on('connection', function(socket) {
  ...
  tail.stdout.on('data', function(chunk) { ... });
  ...
});

这会为每个传入连接添加一个新的处理程序。但是,一旦套接字断开连接,这些不会奇迹般地消失,因此它们会继续尝试通过套接字推送新数据(无论它是否断开连接)。这基本上是一个事件处理程序泄漏,因为它们没有得到清理。

要清理处理程序,您需要保留对处理程序函数的引用并将其作为disconnect 事件处理程序中的侦听器删除:

var handler = function(chunk) { ... }:
tail.stdout.on('data', handler)

socket.on('disconnect', function() {
  tail.stdout.removeListener('data', handler);
});

如果在forEach() 完成之前关闭套接字,您也有可能会从 MongoDB 代码中获得忽略的数据包写入,但这可能是可以接受的(因为数据量是有限的)。

PS:最终,您应该考虑将处理代码(handler 正在执行的操作)移到套接字代码之外,因为它现在正在为每个连接的套接字运行。您可以创建一个单独的事件发射器实例,该实例将发出已处理的数据,并从每个新的套接字连接订阅该事件(并在它们断开连接时再次取消订阅),因此它们只需将已处理的数据传递给客户端。

【讨论】:

  • 谢谢 - 只是运行一个快速测试看看会发生什么......我确实想知道在我的项目开始时我是否在每次有人连接时都调用cp.spawn 但对于少数用户并且只测试从来没有想过更多。我猜现在我的数据库连接也有同样的问题,我在每次连接时都调用数据库,并且在我自己之后没有清理
  • 是的!这为我解决了 - 谢谢。现在,一旦客户离开,被忽略的消息也会消失。我觉得有点傻,因为我知道这是一个“事件处理程序泄漏”,然后我会发现 #28773807
【解决方案2】:

这很可能是由于您的连接是通过polling 传输建立的,这对开发人员来说太痛苦了。原因是此传输使用超时来确定客户端是否在这里。 您看到的行为是由于客户端已离开但下一个轮询会话打开时刻尚未到来,因此服务器仍然认为客户端“它在那里”。

我尝试以多种方式“解决”这个问题(例如在客户端添加自定义 onbeforeunload 事件以强制断开连接),但是当 polling 用作传输时,它们在 100% 的情况下都不起作用.

【讨论】:

  • 稍后我会在有空闲时间的时候进行一些测试,看看如果我只使用 websocket 连接会发生什么。我怀疑我需要保留轮询选项以避免防火墙和某些移动网络/设备出现问题。但是从你所说的关于客户端超时等。如果你看到我的日志条目,它似乎知道客户端已经通过下一个 ping,所以我希望它在它自己之后清理......??跨度>
  • 尝试强制仅使用 websocket 连接,但客户端拒绝连接。他们都喜欢从轮询开始,然后升级到 WS。
  • 无法使用 Web 套接字传输初始化连接是我的第二个痛点。我什至尝试过一次联系 heroku 支持,但没有成功。
  • 查看接受的答案。在这种情况下,结果证明是“事件处理程序泄漏”。
猜你喜欢
  • 1970-01-01
  • 2017-07-17
  • 2012-05-17
  • 1970-01-01
  • 2015-07-28
  • 2018-02-09
  • 2015-04-30
  • 2014-12-16
  • 2012-10-02
相关资源
最近更新 更多