socket.io如何顺序发送多个消息?

时间:2018-07-17 03:52:46

标签: node.js socket.io

我正在这样使用socket.io

客户

socket.on('response', function(i){
    console.log(i);
});
socket.emit('request', whateverdata);

服务器:

socket.on('request', function(whateverdata){
    for (i=0; i<10000; i++){
         console.log(i);
         socket.emit('response', i);
    }
    console.log("done!");
});

两个端子并排放置时,我需要这样的输出:

Server    Client
0         0
1         1
. (etc)   .
.         .
9998      9998
9999      9999
done!

但是我却得到了:

Server    Client
0         
1         
. (etc)         
.         
9998         
9999         
done!
          0
          1
          .
          . (etc)
          9998
          9999

为什么?

Socket.IO / Node是否应该立即发出消息,而不是在发出任何消息之前不等待循环完成?

注意:

  1. for循环非常长且计算速度很慢。
  2. 这个问题是指socket.io库,而不是一般的websockets。
  3. 由于延迟,无法在发送每个响应之前等待客户端的确认
  4. 接收消息的顺序并不重要,只是尽快接收它们

1 个答案:

答案 0 :(得分:4)

服务器将它们全部循环发出,它们花一点时间才能到达客户端并由客户端在另一个进程中进行处理。这不足为奇。

node.js中Javascript的单线程性质也有可能阻止发射实际发送,直到Javascript循环完成为止。这将需要对socket.io代码进行详细检查,以确保确定是否存在问题。正如我之前说的,如果要先发送1,1,然后发送2,2,然后发送3,3,而不是发送1,2,3,然后再发送1,2,3,则必须编写代码以强制执行此操作。

如果要在服务器发送第二个请求之前让客户端接收第一个请求,则必须使客户端向第一个发送响应,并让服务器直到从第一个接收到响应才发送第二个请求。这都是异步网络。除非您编写用于强制执行特定序列的特定代码,否则您无法控制不同进程中事件的顺序。

此外,无论如何,您如何在同一控制台中拥有客户端和服务器?除非您写出精确的时间戳,否则您将无法在两个单独的过程中准确分辨出哪个事件先于另一个事件。


您可以尝试的一件事是发送10个,然后执行setTimeout(fn, 1)发送下一个10,依此类推。这将使JS有机会喘口气,也许还可以处理其他一些事件,这些事件正在等待您完成以允许发送数据包。


还有另一个网络问题。默认情况下,TCP尝试分批发送(最低的TCP级别)。每次发送时,它都会设置一个短计时器,并且直到该计时器触发时才真正发送。如果在计时器启动之前有更多数据到达,它将只是将该数据添加到“待处理”数据包中并再次设置计时器。这称为Nagle's algorithm。您可以使用socket.setNoDelay()逐个套接字禁用此“功能”。您必须在实际的TCP套接字上调用它。

我看到一些讨论,关于Nagle的算法可能已针对socket.io关闭(默认情况下)。不确定。


在逐步完成socket.io的.emit()的过程中,在某些情况下,套接字被标记为尚未写入。在那些情况下,数据包将被添加到缓冲区,并在事件循环的将来某个时间滴答中被“稍后”处理。我看不到是什么使套接字暂时处于这种状态,但我肯定已经看到它在调试器中发生。那样的话,.emit()的紧密循环只会缓冲而不会发送,直到您在事件循环过程中允许其他事件为止。这就是为什么如此频繁setTimeout(fn, 0)以保持发送然后让先前的数据包进行处理的原因。在socket.io使套接字再次可写之前,还需要处理其他事件。

该问题发生在engine.io(socket.io的传输层)的flush()方法中。这是code for .flush()

Socket.prototype.flush = function () {
  if ('closed' !== this.readyState &&
                this.transport.writable &&
                this.writeBuffer.length) {
    debug('flushing buffer to transport');
    this.emit('flush', this.writeBuffer);
    this.server.emit('flush', this, this.writeBuffer);
    var wbuf = this.writeBuffer;
    this.writeBuffer = [];
    if (!this.transport.supportsFraming) {
      this.sentCallbackFn.push(this.packetsFn);
    } else {
      this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn);
    }
    this.packetsFn = [];
    this.transport.send(wbuf);
    this.emit('drain');
    this.server.emit('drain', this);
  }
};

有时会发生this.transport.writable为假的情况。而且,发生这种情况时,它还不会发送数据。它将在事件循环的将来某个时间发送。

据我所知,问题似乎在WebSocket code中:

WebSocket.prototype.send = function (packets) {
  var self = this;

  for (var i = 0; i < packets.length; i++) {
    var packet = packets[i];
    parser.encodePacket(packet, self.supportsBinary, send);
  }

  function send (data) {
    debug('writing "%s"', data);

    // always creates a new object since ws modifies it
    var opts = {};
    if (packet.options) {
      opts.compress = packet.options.compress;
    }

    if (self.perMessageDeflate) {
      var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;
      if (len < self.perMessageDeflate.threshold) {
        opts.compress = false;
      }
    }

    self.writable = false;
    self.socket.send(data, opts, onEnd);
  }

  function onEnd (err) {
    if (err) return self.onError('write error', err.stack);
    self.writable = true;
    self.emit('drain');
  }
};

在发送一些数据直到确认已写入数据之前,您可以在其中看到.writable属性设置为false。因此,在循环中快速发送数据时,可能不会让事件通过表明数据已成功发送的信号。当您执行setTimeout()以便处理事件循环中的某些事件时,确认事件就会通过,并且.writable属性再次设置为true,因此可以立即再次发送数据。 / p>

说实话,socket.io是由数十个模块中的许多抽象层组成的,因此很难在GitHub上调试或分析代码,因此很难确定确切的解释。我确实在调试器中确实看到.writable标志为false的确引起了延迟,因此这对我来说似乎是一个合理的解释。我希望这会有所帮助。