【求助】【再次描述问题】Nodejs WebSocket 消息有时延迟很久。

【求助】【再次描述问题】Nodejs WebSocket 消息有时延迟很久。

2014-8-19再次描述问题。

有时客户端在发送二进制数据时,服务端收到时少了一个字节,这时服务器会等待这个字节,到来之后才调用onMessage。 然而这个字节是在一段时间(20s)没有通信,服务器发送ping消息,客户端回应pong时。和pong一起收到的。 客户端发送的时候是没有问题的。 应该不是WebSocket包的原因。 有没有大神帮忙分析一下。。

###↓↓↓这是之前的描述 使用WebSocket 客户端发送message到服务器端,即使是在本地有时会有很长的延迟(10s左右)才收到

项目使用的websocket包是Worlize/WebSocket-Node 简单调试了一下源码,发现当需要接收的数据包多于一个的时候,第二个数据包会在发送后很久才到达。 (目前我还不清楚数据包是怎样分的,同样的请求数据有时一个包,有时两个包。)

源码里有关buffer的东西太多,有点弄不懂了。。

有没有遇到过同样问题的大神帮忙指点指点

今天把WebSocket包换成了einaros/ws,问题反而更严重了。

在ws/lib/Receiver.js中有一个expectData方法,其功能就是按照长度length保存WebSocket包的data

Receiver.prototype.expectData = function(length, handler) {
  if (length == 0) {
    handler(null);
    return;
  }
  this.expectBuffer = this.allocateFromPool(length, this.state.fragmentedOperation);
  this.expectHandler = handler;
  var toRead = length;//需要读取的长度
  while (toRead > 0 && this.overflow.length > 0) {
    var fromOverflow = this.overflow.pop();//第一个包(一般只有一个,而且fromOverflow.length=toRead,
                                       //但是问题出现的时候fromOverflow.length=toRead-1,也就是少一个字节)
                                       //正常情况下会再循环一次读取下一个字节,可问题是下一个包还没收到 this.overflow.length = 0,所以并没有循环读取,而是直接返回了
    if (toRead < fromOverflow.length) this.overflow.push(fromOverflow.slice(toRead));
    var read = Math.min(fromOverflow.length, toRead);
    fastCopy(read, fromOverflow, this.expectBuffer, this.expectOffset);
    this.expectOffset += read;
    toRead -= read;
  }
};

为什么第二个包接收到的这么慢呢? 再深层的代码就是nodejs的net.js 和 _stream_readable.js了。

再次求助啊。。谢谢大家了


11 回复

根据您的描述,这个问题主要涉及到WebSocket消息传输过程中可能出现的数据包丢失或延迟。这种情况可能是由于网络问题、缓冲区管理不当或WebSocket库本身的bug导致的。

分析与建议

1. 网络问题

首先,检查网络连接是否稳定。如果网络不稳定,可能会导致数据包丢失或延迟。可以尝试在不同的网络环境下测试,以确定是否是网络问题。

2. 缓冲区管理

从您的描述来看,问题可能出在缓冲区管理上。WebSocket协议在传输数据时,会将数据分割成多个数据包进行传输。接收端需要正确处理这些数据包,否则可能导致数据丢失或延迟。

3. WebSocket库

您提到使用的是einaros/ws库,并且在更换库后问题变得更严重。这可能是因为不同库对WebSocket协议的实现方式不同,导致某些情况下处理不当。

示例代码及解释

以下是一个简单的Node.js WebSocket服务器和客户端示例,使用ws库。您可以尝试使用这个库并查看是否仍然存在延迟问题。

服务器端代码

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', ws => {
  console.log('Client connected');
  
  ws.on('message', message => {
    console.log(`Received message: ${message}`);
  });
  
  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

console.log('WebSocket server is running on ws://localhost:8080');

客户端代码

const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:8080');

ws.on('open', () => {
  console.log('Connected to server');
  setInterval(() => {
    ws.send(new Uint8Array([1, 2, 3, 4]));
    console.log('Sent message');
  }, 1000);
});

ws.on('message', message => {
  console.log(`Received message: ${message}`);
});

ws.on('close', () => {
  console.log('Disconnected from server');
});

调试与优化

  1. 日志记录:在关键位置添加日志记录,以便更好地理解数据包的传输过程。
  2. 超时设置:增加超时时间,确保在网络不稳定的情况下不会导致数据包丢失。
  3. 错误处理:增强错误处理逻辑,确保在网络异常时能够及时恢复。

希望这些信息能帮助您解决问题。如果问题仍然存在,请提供更多详细信息以便进一步分析。


好像用socket.io会简单些. 如果只要纯websoscket的话, 这个库会简单些ws

有没有nodelay之类的设置?

我之前有弄个,怎么感觉没什么延时 用的是 ws

再次求助啊。。谢谢大家了

这库的代码没法看,说说思路看看你能不能找到问题吧 1.抓包检查少的字节是否确实已经从客户端发送出去了,如果未发送,检查客户端的发送buffer的实现 2.在服务端抓包看是否收到了这个字节,如果收到了,检查服务端接收buffer的实现,因为消息可能是达到一个阈值才会触发onMessage事件的 3.在库的Receiver和buffer实现里多打一点log看

最近才用websocket-node写了个服务器 https://github.com/Hi-Rube/RChat-Server android客户端2g网络发消息秒速到达,没觉得有延迟啊~

请问楼主找到解决方案来吗?求教!

是不是采用了默认的Nagle算法,该算法在网络中存在极少数有效数据的时候不会立即发送,只有等到下一批数据到来时才会发,可以通过socket.setNoDelay(true)解除该算法

expectData需要传入 length 参数,会不会是这个参数有问题,少了1?

从你的描述来看,问题可能出在WebSocket库对数据包的处理方式上。在einaros/ws库中,Receiver.prototype.expectData方法用于处理接收到的数据片段,并根据预期的数据长度来处理这些片段。如果某些片段没有及时到达,可能会导致延迟。

为了简化问题,你可以尝试以下几点:

  1. 使用其他WebSocket库:有时候更换不同的WebSocket库可以解决一些特定的问题。例如,可以尝试使用ws库的最新版本,或者尝试其他如socket.io这样的库。

  2. 增加心跳检测:在长时间没有消息传递时,增加心跳检测可以帮助保持连接活跃。可以在客户端和服务端都实现一个心跳机制,通过定期发送和响应心跳消息来确保连接稳定。

  3. 优化缓冲区管理:在接收数据时,可以检查是否有足够的缓冲空间来处理所有数据。如果有需要,可以调整缓冲区大小或优化缓冲区的分配策略。

  4. 调试和日志记录:增加详细的日志记录可以帮助你追踪数据包的接收情况,找到延迟的具体原因。

以下是使用ws库的一个简单示例,演示如何设置一个基本的心跳机制:

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', ws => {
  let timeout;

  const heartbeat = () => {
    clearTimeout(timeout);
    timeout = setTimeout(() => ws.terminate(), 30000); // 如果30秒没有收到消息,则断开连接
  };

  ws.on('pong', heartbeat);

  ws.on('message', message => {
    console.log('received: %s', message);
    heartbeat(); // 接收到消息后重置心跳计时器
  });

  heartbeat();
});

这个示例展示了如何在ws库中设置心跳机制。希望这些建议对你有所帮助!

回到顶部