如何使用套接字(socket.io)在 NodeJS 中找到客户端的响应时间(延迟)?

Posted

技术标签:

【中文标题】如何使用套接字(socket.io)在 NodeJS 中找到客户端的响应时间(延迟)?【英文标题】:How can I find the response time (latency) of a client in NodeJS with sockets (socket.io)? 【发布时间】:2011-05-03 13:03:44 【问题描述】:

我正在尝试使用 NodeJS 创建多人游戏,并且我想在客户端之间同步操作。

找出客户端和服务器之间的延迟(请求返回客户端所需的时间)的最佳方法是什么?

我的第一个想法是客户端 #1 可以发送带有 is request 的时间戳,因此当客户端 #2 收到客户端 #1 的操作时,他将调整操作速度以消除请求的延迟。 但问题是,可能两个客户端的系统日期时间不相同,所以不可能两个知道客户端 #1 请求的卷轴延迟。

另一种解决方案是使用服务器的时间戳,但现在我如何知道客户端的延迟?

【问题讨论】:

【参考方案1】:

阅读所有这些答案后...

...我还是不满意。我访问了官方文档,嗯,嗯,嗯 - 解决方案已经内置了。

你只需要实现它——看看我的:

客户

// (Connect to socket).

var latency = 0;

socket.on('pong', function(ms) 
    latency = ms;

    console.log(latency);
);

// Do cool things, knowing the latency...

 服务器

var server = require('http').Server(app);

// "socket.io": "^1.7.1"
// Set pingInterval to whatever you want - 'pong' gets emitted for you!
var io = require('socket.io')(server, pingInterval: 5000);

【讨论】:

这是最好最简单的答案。 请注意 pong 在 socket.io v3.0 中已弃用。更多信息here. @ItayGanor 让我头疼——谢谢【参考方案2】:

我将假设您正在使用 WebSockets 或 Socket.IO,因为您正在实施一款延迟很重要的游戏(并且您已将其标记为此类)。

我认为服务器可能应该为每个客户端测量并跟踪这一点。

您可能希望实现某种服务器可以请求客户端的 ping 操作。一旦客户端收到请求,它就会向服务器发送回响应。然后服务器除以 2 并更新该客户端的延迟。您可能希望服务器定期对每个客户端执行此操作,并可能平均最后几个客户端,这样您就不会因为突然但暂时的峰值而出现奇怪的行为。

然后,当有来自一个客户端的消息需要发送(或广播)到另一个客户端时,服务器可以将客户端 1 的延迟添加到客户端 2 的延迟,并将其作为延迟偏移作为消息的一部分传递给客户端 2。然后,client2 将知道 client1 上的事件发生在几毫秒之前。

在服务器上执行此操作的另一个原因是某些浏览器 javascript 时间戳不准确:http://ejohn.org/blog/accuracy-of-javascript-time/。我怀疑 node.js 时间戳与 V8 一样准确(或更准确)(这是为数不多的准确时间戳之一)。

【讨论】:

是的,我正在使用“socket.io”。这正是我已经实现的,我忘记的是将延迟除以 2!感谢您的建议! 嘿 FR6,我实际上只是实现了一个 socket.io ping 测试,因为我也有兴趣使用 node.js 作为服务器来制作网络浏览器游戏。我会写一篇关于它的博客文章并将其链接在这里,希望它会有所帮助。出于某种原因,我的 websocket ping 平均 ping 时间约为 220 毫秒(往返时间),但是 @FR6, @Travis:开源代码(和博客)会很棒。我相信很多人都会对此感兴趣。 +1 对此进行探索。希望看到一些延迟指标并确定响应关键应用程序是否最终可以在客户端 js 中使用(我想像按钮捣碎器、街头霸王风格游戏等等)【参考方案3】:

概述:

socket.io 连接建立后,在客户端创建一个新的Date 对象,我们称之为startTime。这是您向服务器发出请求之前的初始时间。然后,您从客户端发出 ping 事件。命名约定完全取决于您。同时,服务器应该正在监听ping 事件,当它收到ping 时,它会立即发出pong 事件。客户端然后捕获pong 事件。此时您要创建另一个代表Date.now() 的日期对象。因此,此时您有两个日期对象 - 向服务器发出请求之前的初始日期,以及在您向服务器发出请求并回复之后的另一个日期对象。从当前时间减去startTime,得到latency

客户

var socket = io.connect('http://localhost');
var startTime;

setInterval(function() 
  startTime = Date.now();
  socket.emit('ping');
, 2000);

socket.on('pong', function() 
  latency = Date.now() - startTime;
  console.log(latency);
);

服务器

io.sockets.on('connection', function (socket) 
  socket.on('ping', function() 
    socket.emit('pong');
  );
);

也可作为Github Gist 使用。

【讨论】:

这会每 2 秒在“pong”上添加另一个回调。 socket.on('pong' 的东西应该在 setInterval 之外。 @PanMan 犯了我的愚蠢错误,感谢您发现它。 startTime 必须是全局的,代码才能正常工作,仅供参考 :) 谢谢。 对我来说,使用“ping”这个词是行不通的。我认为它已经被 node.js 使用了。我把它改成了不同的东西,它起作用了。 注意: ping 和 pong 作为消息名称已被 socket.io 本身使用socket.io/docs/emit-cheatsheet【参考方案4】:

我通常用请求发送时间戳:

    在客户端上,创建一个 new Date() 并将 timestamp: date.getTime() 发送到服务器,每个 JSON 请求。 在服务器上,收到请求后,将processed: (new Date()).getTime() 放入对象中。 处理请求。 在响应中,将请求中的 timestamp 和一个新的已处理字段:processed: (new Date()).getTime() - req.processed 现在包含处理请求所用的毫秒数。 在客户端,当接收到响应时,将timestamp(与pt 1上发送的相同)从当前时间中减去,再减去处理时间(processed),有您的“真实”ping 时间(以毫秒为单位)。

我认为您应该始终在 ping 时间中包含请求和响应的时间,即使是单向通信也是如此。这是因为这是“ping time”和“latency”背后的标准含义。而如果是单向通信,延迟只有真实 ping 时间的一半,那简直是“好事”。

【讨论】:

这是我第一次做的。但不起作用的是客户端 #1 和客户端 #2 可能没有相同的日期时间(在不同的时区)或不同步。就像 kanaka 所说的那样,JavaScript 日期时间在客户端可能不准确。 它是否准确与任何事情无关。每个客户端(或服务器)只使用该实例创建的日期。根据请求发送的时间戳会完全按原样返回给客户端。因此,客户端可以在自己的时间看到自请求发送以来已经过了多长时间。 对我来说很有意义。它可以防止您在其他呼叫之间进行单独的“ping”呼叫。有没有办法让 socket.io 自动包含这个?【参考方案5】:

这是我测试 ping 的快速而肮脏的脚本......只需在浏览器中前往 http://yourserver:8080 并观看控制台(我的 ssh 终端)。

var http = require('http');
var io = require('socket.io');

server = http.createServer(function (req, res) 
  res.writeHead(200, 'Content-Type': 'text/html');
  res.write('<html>\n');
  res.write('  <head>\n');
  res.write('    <title>Node Ping</title>\n');
  res.write('    <script src="/socket.io/socket.io.js"></script>\n');
  res.write('    <script>\n');
  res.write('        var socket = new io.Socket();\n');
  res.write('        socket.on("connect",function() );\n');
  res.write('        socket.on("message",function() socket.send(1); );\n');
  res.write('        socket.connect();\n');
  res.write('    </script>\n');
  res.write('  </head>\n');
  res.write('  <body>\n');
  res.write('    <h1>Node Ping</h1>\n');
  res.write('  </body>\n');
  res.write('</html>\n');
  res.end();
);
server.listen(8080);

console.log('Server running at http://127.0.0.1:8080/');

var socket = io.listen(server);

socket.on('connection',function(client)
  var start = new Date().getTime();
  client.send(1);
  client.on('message',function(message) client.send(1);  console.log( new Date$
  client.on('disconnect',function());
);

我对此非常好奇,因为在加利福尼亚州和新泽西州使用专用资源的大型 vps 盒子上,我的 ping 似乎相当高(往返 200-400 毫秒)。 (我在东海岸)我敢打赌 vps 盒子 b/c 上只有很多延迟,他们服务这么多流量?

让我感到惊讶的是,从 linux 终端从同一客户端到同一服务器的常规 ping 平均为 11 毫秒,降低了 10 倍……我做错了什么还是 node.js 速度慢/socket.io/websockets?

【讨论】:

您在什么浏览器上进行测试?因为当我在 Chrome 上进行一些测试时,PING 大约是 40 毫秒,而在 Firefox 3.6.x 中大约是 350 毫秒。我想这些差异主要是因为 Firefox 3.6 本身不支持 websockets,所以模块“socket.io”将使用 Flash 来创建服务器和客户端之间的连接。也许延迟是由于浏览器使用 JavaScript 与 Flash 通信所需的时间造成的。另一方面,当浏览器原生支持 websocket 时,他不需要使用 Flash。 我正在使用最新的一款支持 websockets 传输选项的 chrome 夜宵。我明白你所说的关于 firefox/flash 减速的说法,甚至还没有开始担心。 在最近一次更新 socket.io 之后,服务器响应时间似乎下降到了 ~10 毫秒!【参考方案6】:

请先阅读——由于重复的问题,为什么这应该起作用,让我澄清一下。

客户端回调函数在客户端上执行,这就是它可以访问闭包的原因,包括包含时间戳的start 变量。这是the ack() argument in socket.io. 服务器自然不能在客户端调用任意函数并访问函数的闭包。但是socket.io 允许定义一个回调函数,看起来是由服务器执行,但这实际上只是通过 web 套接字传递函数参数,然后客户端调用回调。李>

下面会发生什么(请检查示例代码!):

    客户端将当前时间戳1453213686429 存储在start 中 客户端向服务器发送ping 事件并等待答复 服务器以“请使用空参数调用您的回调”响应 ping 事件 客户端收到响应并使用空参数调用clientCallback(如果您想查看参数,请查看演示代码) clientCallback 再次获取当前时间戳在客户端,例如1453213686449,并且知道 20 ms 自发送请求以来已过去。

想象德鲁伊 (client) 拿着秒表并在信使 (event) 开始运行时按下按钮,并在信使带着他的卷轴到达时再次按下按钮(函数参数)。然后德鲁伊阅读卷轴并将成分名称添加到他的药水配方中并酿造药水。 (回调)

好吧,忘记上一段,我想你明白了。


虽然问题已经得到解答,但这里有一个简短的实现,用于使用 socket.io 检查 RTT:

客户

var start = Date.now();
this.socket.emit( 'ping', function clientCallback() 
    console.log( 'Websocket RTT: ' + (Date.now() - start) + ' ms' );
 );

服务器

socket.on( 'ping', function ( fn ) 
    fn(); // Simply execute the callback on the client
 );

演示代码

作为节点模块的演示代码:socketIO-callback.tgz 设置并运行它

npm install
node callback.js

然后导航到http://localhost:5060

【讨论】:

你真的认为你可以将回调引用作为数据发送到服务器,然后让它在服务器上执行,并且以某种方式让回调甚至能够访问客户端上的闭包变量吗?这甚至不会接近工作。 他没有传递要调用的 fn,而是调用客户端回调来说“我知道了”。它是 socket.io 的事情。 我真的很喜欢这种方法。我们使用回调的执行点作为检查时间的地方。 这行不通!您在客户端上使用日期时间,即 pc/client 的日期时间,因此如果客户端将其 pc 时间设置为 2004 年 1 月 1 日,则 ping 将超过十年。想想吧。 @zazu 不。检查示例代码。 回调也在客户端执行。 请注意,服务器不会访问客户端,它只是要求客户端使用提供的参数(如果有)调用回调函数。这没有什么魔力。

以上是关于如何使用套接字(socket.io)在 NodeJS 中找到客户端的响应时间(延迟)?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用套接字(socket.io)在 NodeJS 中找到客户端的响应时间(延迟)?

npm安装socket.io时报错的解决方法(npm WARN enoent ENOENT: no such file or directory, open '/usr/local/nodej

如何在 Express 4 路由中使用 socket.io 向连接的套接字发出事件?

Socket.io 与 flask-socketio python。如何设置套接字保持活动/超时

如何在Socket.IO中一次性离开套接字连接的所有房间[关闭]

如何使用 socket.io、react native、nodejs 管理聊天应用程序的多个套接字连接