Socket.io 1.3.7没有清理客户端断开连接

时间:2015-12-06 16:54:17

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

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

我刚刚升级了node.js& socket.io到当前版本(来自< 0.9)并且我试图了解当客户端退出,超时或断开与服务器的连接时会发生什么。

这是我当前的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知道这一点。那么为什么我仍然会看到所有忽略的数据包写入&#39;每个已断开连接的客户都会看到更多行。

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

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

更新3:通过@robertklep回答这是一个“事件处理程序泄漏”问题。我真的在寻找。发现我还发现了this帖子。

2 个答案:

答案 0 :(得分:0)

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

我曾试图&#34;战斗&#34;这个问题在很多方面(比如在客户端添加自定义onbeforeunload事件以强行断开连接)但是当polling用作传输时,它们都不能在100%的情况下工作。

答案 1 :(得分:0)

我的猜测是,较新的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正在做什么)移到套接字代码之外,因为它现在正在为每个连接的套接字运行。您可以创建一个单独的事件发射器实例,它将发出已处理的数据,并从每个新的套接字连接订阅(并在断开连接时再次取消订阅),因此他们只需将处理后的数据传递给客户端。