socket.io: 断开事件 - 'transport close','client namespace disconnect','transport error'和 'forced close'

19
使用socket.io v1.2.1(仅使用“轮询”传输),有时客户端会出现断开连接的情况。
大约50%的时间,我在我的断开连接事件回调函数上得到“ping timeout”,这是合理的。
其他时候,我得到“transport close”,“client namespace disconnect”,“transport error”和“forced close”。我没有在文档中找到这些断开连接原因的参考,并且无法真正从代码中理解它们的含义。
我想确保我以最佳方式处理每个断开连接(并可能防止它们)。
也许有人可以稍微解释一下这些原因。
4个回答

6

没有文档,以下是我从代码中解释出来的:

强制关闭 - 套接字处于关闭状态

强制关闭 - https://github.com/socketio/engine.io/blob/master/lib/socket.js

function onPacket(packet){
    if ('ping' == packet.type && 'probe' == packet.data) {
      transport.send([{ type: 'pong', data: 'probe' }]);
      self.emit('upgrading', transport);
      clearInterval(self.checkIntervalTimer);
      self.checkIntervalTimer = setInterval(check, 100);
    } else if ('upgrade' == packet.type && self.readyState != 'closed') {
      debug('got upgrade packet - upgrading');
      cleanup();
      self.upgraded = true;
      self.clearTransport();
      self.setTransport(transport);
      self.emit('upgrade', transport);
      self.setPingTimeout();
      self.flush();
      if (self.readyState == 'closing') {
        transport.close(function () {
          self.onClose('forced close');
        });
      }
    } else {
      cleanup();
      transport.close();
    }
  }


Socket.prototype.close = function () {
  if ('open' != this.readyState) return;

  this.readyState = 'closing';

  if (this.writeBuffer.length) {
    this.once('drain', this.closeTransport.bind(this));
    return;
  }

  this.closeTransport();
};

传输已经关闭(没有原因)

Transport close - https://github.com/socketio/engine.io/blob/master/lib/socket.js

 function cleanup() {
    self.upgrading = false;

    clearInterval(self.checkIntervalTimer);
    self.checkIntervalTimer = null;

    clearTimeout(self.upgradeTimeoutTimer);
    self.upgradeTimeoutTimer = null;

    transport.removeListener('packet', onPacket);
    transport.removeListener('close', onTransportClose);
    transport.removeListener('error', onError);
    self.removeListener('close', onClose);
  }


  function onTransportClose(){
    onError("transport closed");
  }

我们收到了一个客户端断开连接的数据包,所以我们将socket状态更改为“closing”。

客户端命名空间断开连接 - https://github.com/socketio/socket.io/blob/master/lib/socket.js

Socket.prototype.onpacket = function(packet){
  debug('got packet %j', packet);
  switch (packet.type) {
    case parser.EVENT:
      this.onevent(packet);
      break;

    case parser.BINARY_EVENT:
      this.onevent(packet);
      break;

    case parser.ACK:
      this.onack(packet);
      break;

    case parser.BINARY_ACK:
      this.onack(packet);
      break;

    case parser.DISCONNECT:
      this.ondisconnect();
      break;

    case parser.ERROR:
      this.emit('error', packet.data);
  }
};


Socket.prototype.ondisconnect = function(){
  debug('got disconnect packet');
  this.onclose('client namespace disconnect');
};

传输关闭的原因之一

传输错误 - https://github.com/socketio/engine.io/blob/master/lib/socket.js

/**
 * Called upon transport error.
 *
 * @param {Error} error object
 * @api private
 */

Socket.prototype.onError = function (err) {
  debug('transport error');
  this.onClose('transport error', err);
};

https://github.com/socketio/engine.io/blob/master/lib/transport.js

/**
 * Called with a transport error.
 *
 * @param {String} message error
 * @param {Object} error description
 * @api private
 */

Transport.prototype.onError = function (msg, desc) {
  if (this.listeners('error').length) {
    var err = new Error(msg);
    err.type = 'TransportError';
    err.description = desc;
    this.emit('error', err);
  } else {
    debug('ignored transport error %s (%s)', msg, desc);
  }
};

看起来它们会从各个方面向套接字抛出错误,因此找到原因的唯一方法是通过阅读错误描述(并不是太多信息)或查找所有库以找出导致错误的原因。

附注:有很多错误。


404? https://github.com/socketio/engine.io/blob/main/lib/socket.ts - Devashish

1
尝试在服务器端运行此代码。
var fs = require('fs');
var pkey = fs.readFileSync('/etc/ssl/private/ssl.key'); //Replace the path of your SSL key
var pcert = fs.readFileSync('/etc/ssl/certs/ssl.crt');//Replace the path of your SSL cert
var options = {
  key: pkey,
  cert: pcert
};

var app = require('https').createServer(options);

var io = require('socket.io')(app, {'pingTimeout': 180000, 'pingInterval': 25000});

这里 pingInterval 很重要,保持低值,我尝试了各种值,发现25秒是不会让套接字在超时之前停止ping的好值。
主要问题在于如果在60秒内没有ping/pong,则会断开连接并尝试自动重新连接。此外,我发现服务器端的 pingTimeout 和客户端端的超时都无法帮助在60秒内断开套接字。这是由于Chrome最新版本83引起的。

请查看以下链接获取更多信息:https://github.com/socketio/engine.io-protocol#difference-between-v3-and-v4 - Gopal Kohli

0

我曾经遇到过类似的情况,服务器向客户端发送了太多更新(在几秒钟内达到了约2000次),导致连接被强制关闭。降低更新频率可以解决这个问题。

我还没有找到这是由socket.io本身引起的问题,还是由防火墙/服务器机制引起的问题。也许是因为洪水般的连接干扰了心跳机制,尽管客户端日志并未显示出来。

虽然确切的原因尚未找到,但也许这可以指引一些人朝着正确的方向寻找解决方案。


对我们来说,问题似乎是事件循环在特定阶段“卡住”了,因此需要很长时间才能回到socket.io的内容,导致socket.io心跳超时并且必须重新连接。这在https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/中有描述。我们的解决方案是批处理类似类型的“阶段操作”,以防止事件循环在一个阶段中过长时间。它解决了我们的超时问题。也许它可以帮助到某些遇到这个问题的人。 - Centillion

-2

不幸的是,这种情况可能会发生。有时候,就像我不幸遇到的那样,这是由于位于我、服务器和其他客户端之间的防火墙。

对于 ping 超时,您可以尝试增加服务器端的 ping 间隔。

io = require( 'socket.io' )( httpServer, { pingInterval: 60000 } );

3
我认为,增加“pingInterval”值只是一种解决方法,而不是真正的解决方案。这会导致实际上已经失去连接的客户端仍然看起来像是连接着。 - Gilad Artzi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接