30

I'm trying to create a multiplayer game with NodeJS and I want to synchronize the action between clients.

What would be the best way to find the latency (the time that a request take to come back to the client) between the client and the server?

My first idea was that the client #1 could send a timestamp with is request, so when client #2 will receive the action of the client #1 he will adjust is action speed to remove the delay of the request. But the problem is that maybe the system date time of the two clients are not identical so it is not possible two know the reel delay on the request of client #1.

The other solution was to use the timestamp of the server, but now how can I know the latency of a client?

Pooya Estakhri
  • 1,129
  • 1
  • 11
  • 25
FR6
  • 3,157
  • 3
  • 24
  • 28

6 Answers6

34

After reading all these answers...

...I still wasn't satisfied. I visited the official docs and well, well, well - the solution is already built-in.

You just need to implement it - check out mine:

Client

// (Connect to socket).

var latency = 0;

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

    console.log(latency);
});

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

 Server

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});
Luka
  • 603
  • 6
  • 12
27

I'm going to assume you are using WebSockets or Socket.IO since you are implementing a game where latency matters (and you tagged it as such).

I would think the server should probably measure and keep track of this for each client.

You probably want to implement some sort of ping action that the server can request of the client. As soon as the client receives the request, it sends back a response to the server. The server then divides by 2 and updates the latency for that client. You probably want the server to do this periodically with each client and probably average the last several so that you don't get strange behavior from sudden but temporary spikes.

Then, when there is a message from one client that needs to be sent (or broadcast) to another client, the server can add client1's latency to client2's latency and communicate this as the latency offset to client2 as part of the message. client2 will then know that the event on client1 happened that many milliseconds ago.

An additional reason to do this on the server is that some browser Javascript timestamps are inaccurate: http://ejohn.org/blog/accuracy-of-javascript-time/. I suspect node.js timestamps are just as accurate (or more so) than V8 (which is one of the few accurate ones).

kanaka
  • 70,845
  • 23
  • 144
  • 140
  • 3
    Yes i'm using "socket.io". That exactly what I have implemented, the thing that I forget is to divide the latency by 2! Thanks for your advices! – FR6 Nov 02 '10 at 14:54
  • Hey FR6, I actually just implemented a socket.io ping test because I too am interested in making web browser games using node.js as the server. I'll write a blog post about it and link it here, hopefully it will help. For some reason I've been getting ping times of about 220 ms on average for the websocket ping (roundtrip time) but – Travis Nov 03 '10 at 11:53
  • @FR6, @Travis: open source code (and blog) for this would be great. I'm sure lots of people will be interested in this going forward. – kanaka Nov 03 '10 at 13:48
  • +1 for an exploration of this. Would love to see some latency metrics and determine if response critical applications are finally possible in client side js (im thinking like button masher, street fighter style games and whatnot) – DeaconDesperado May 06 '13 at 15:21
24

Overview:

After socket.io connection has been established, you create a new Date object on the client, let's call it startTime. This is your initial time before making a request to the server. You then emit a ping event from the client. Naming convention is totally up to you. Meanwhile server should be listening for a ping event, and when it receives the ping, it immediately emits a pong event. Client then catches the pong event. At this time you want to create another date object that represents Date.now(). So at this point you have two date objects - initial date before making a request to the server, and another date object after you make a request to the server and it replied. Subtract the startTime from current time and you have the latency.

Client

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);
});

Server

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

Also available as a Github Gist.

Sahat Yalkabov
  • 32,654
  • 43
  • 110
  • 175
6

What I usually do to send timestamp with request:

  1. On the client, create a new Date() and send timestamp: date.getTime() to the server, with every JSON request.
  2. On the server, upon receiving a request, put a processed: (new Date()).getTime() in the object.
  3. Handle request.
  4. On the response, put the timestamp from the request, and a new processed field: processed: (new Date()).getTime() - req.processed that now contains the number of milliseconds it took to process the request.
  5. On the client, when receiving a response, take the timestamp (which is the same that was sent on pt 1) and subtract it from the current time, and subtract processing time (processed), and there is your "real" ping time in milliseconds.

I think you should always include the time for both request and response in the ping time, even if there is one-way communication. This is because that is the standard meaning behind "ping time" and "latency". And if it is one-way communication and the latency is only half of the real ping time, that's just a "good thing".

Tor Valamo
  • 33,261
  • 11
  • 73
  • 81
  • This is what I done in the first time. But what does not work is that the client #1 and the client #2 maybe don't have the same date time (on different timezone) or are not synchronized. And like kanaka was saying is that the JavaScript date time is maybe not accurate on the client side. – FR6 Nov 03 '10 at 12:45
  • Whether or not it is accurate has nothing to do with anything. Each client (or server) only ever uses a date that that instance creates. The timestamp that is sent on request, is returned to the client exactly as it was. And so the client can see how long it was since the request was sent, in its own time. – Tor Valamo Nov 03 '10 at 14:50
  • Makes sense to me. It prevents you from having to make a separate 'ping' call between other calls. Is there a way of getting socket.io to include this automatically? – backdesk Dec 08 '14 at 13:33
1

Heres my really quick and dirty script to test the ping ... just head to http://yourserver:8080 in your browser and watch the console (ssh terminal for me).

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(){});
});

I'm very curious about this because it seems like my pings are pretty high(200-400ms round trip) on large vps boxes w/ dedicated resources both in california and new jersey. (I'm on the east coast) I'm betting theres just a lot of latency on the vps boxes b/c they're serving so much traffic?

The thing that gets me is that a regular ping from the linux terminal from the same client to the same server is 11ms on average a factor of 10 lower ... am I doing something wrong or is something slow with node.js/socket.io/websockets?

Travis
  • 7,391
  • 12
  • 43
  • 52
  • On what browser are you making your test? Because when I made some test on Chrome the PING was around 40 ms and in Firefox 3.6.x it was around 350 ms. I suppose the differences is mainly because Firefox 3.6 don't have natively support of websockets so the module "socket.io" will use Flash to create the connection between the server and the client. And maybe the delay is cause by the time that it takes to the browser to communicate with the Flash with JavaScript. On other hand when the browser supports websocket natively, he don't need to use Flash. – FR6 Nov 03 '10 at 12:56
  • I'm working in one of the latest nightlies of chrome that supports the websockets transfer option. I see what you're saying about the firefox/flash slowdown and haven't even started worrying about that yet. – Travis Nov 03 '10 at 13:01
  • After the most recent update of socket.io it seems that the server response times have plummeted down to ~10ms! – Travis Nov 14 '10 at 13:54
0

Read first — Due to repeated questions why this is supposed to work, let me clarify a bit.

  • The client callback function is executed on the client, which is why it has access to the closure, including the start variable containing the time stamp. This is the ack() argument in socket.io.
  • The server naturally cannot call an arbitrary function on the client and access the function’s closure. But socket.io allows to define a callback funtion, which appears to be executed by the server, but this actually just passes the function arguments through the web socket, and the client then calls the callback.

What happens below (please do check the example code!):

  1. Client stores current timestamp 1453213686429 in start
  2. Client sends a ping event to the server and is waiting for an answer
  3. Server responds to the ping event with “Please call your callback with empty arguments”
  4. Client receives the response and calls clientCallback with empty arguments (Check the demo code if you want to see arguments)
  5. clientCallback again takes the current timestamp on the client, e.g. 1453213686449, and knows that 20 ms have passed since it sent the request.

Imagine the druid (client) holding a stopwatch and pushing the button when the messenger (event) starts running, and pushing it again when the messenger arrives with his scroll (function arguments). The druid then reads the scroll and adds the ingredient names to his potion recipe and brews the potion. (callback)

Okay, forget the previous paragraph, I guess you got the point.


Although the question has already been answered, here a short implementation for checking the RTT with socket.io:

Client

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

Server

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

Demo Code

Demo code as node module: socketIO-callback.tgz Set it up and run it with

npm install
node callback.js

and then navigate to http://localhost:5060

Simon A. Eugster
  • 4,114
  • 4
  • 36
  • 31
  • Do you really think you can send a callback reference to the server as data and then have it execute on the server and somehow have that callback even have the ability to access a closure variable on the client? This would not even come close to working. – jfriend00 Oct 14 '14 at 18:46
  • 2
    he isn't passing a fn to be called but rather is calling the client-callback to say "i got it". its a socket.io thing. – japrescott Nov 12 '14 at 00:29
  • I really like this approach. We're using the point at which the callback is executed as a place to check the time. – backdesk Dec 08 '14 at 13:49
  • This will not work! You are taking date time on the client, which is the date time of pc/client, so if client sets its pc time to 01/01/2004 the ping would be more than ten years. Think of it. – iamawebgeek Jan 17 '16 at 16:28
  • @zazu Nope. Check the sample code. **The callback is executed on the client too.** Note that the server does not access the client, it just ask the client to please call the callback function with the arguments provided (if any). There is no magic in that. – Simon A. Eugster Jan 19 '16 at 14:15
  • But you set start variable on client side, so it would be the time set on client's device – iamawebgeek Jan 19 '16 at 14:20
  • @zazu I have extended the answer. Hope it answers the question now. Yes, it is *always* the time of the client device, but you just need to know the delta, does not matter if it is 2016 or 1883. – Simon A. Eugster Jan 19 '16 at 14:40
  • 1
    I can confirm this works, using it in production – Coder Gautam YT Jun 22 '22 at 20:08