Realtime clock synch across browser clients - javascript

I am writing a collaborative music web-app that can be "played" in parallel across browsers in different devices.
Imagine two people standing in the same room, and playing their "instruments" together. Each one holding a different device running a webapp in their hands.
I need both devices to have a synched timer where they agree on what time it is +/- several milliseconds.
My first attempt was simply to trust that my 2 devices (windows PC, and android phone) are synched. But they have several seconds between their clocks. So I realize that I need to implement this myself.
Is there a REST/Websocket service that I can use to periodically synchornize the apps' time?
If not, is there a standard algorithm that would be effective to implement over websocket?
My naiive instinct is to implement a 4 way ping between the client and server, and half their ping/pong time, but I am pretty sure that someone has allready implemented something better.
As we are talking about music, standard multiplayer time-drifts won't cut it. I need the clocks to be in synch in greater accuracy than network ping time.
Is there something like NTP that works in a browser?

This works mostly ok for now.
Some hicups if browser execution is delayed on busy machine.
I echo the requests back to the client with the addition of servertime from the server.
function syncTime(requestTimeMs, serverTimeMs) {
var now = Date.now();
var latency = (now - requestTimeMs) / 2;
var serverTimeWithLatency = serverTimeMs + latency;
timeOffsetFromServer = parseInt(serverTimeWithLatency - now);
}
function getSynchedTimeMs() {
return Date.now() + timeOffsetFromServer
}

Related

How to reconnect socket after a passive network switch

So I have a Swift client, Node.js server, and am using socket.io. I have an issue where when the user changes from WiFi to LTE (passively, if they turn off wifi manually it works fine) while being connected to the server, for some reason they don't reconnect to the server (just hit a ping timeout). I've tried increasing ping timeout to 50 seconds with no effect. My users interact with each other while being connected to the same room so this is a big issue.
My connection code on the client-side looks like this:
var socket: SocketIOClient?
fileprivate var manager: SocketManager?
func establishConnection(_ completion: (() -> Void)? = nil) {
let socketUrlString: String = serverURL
self.manager = SocketManager(socketURL: URL(string: socketUrlString)!, config: [.forceWebsockets(true), .log(false), .reconnects(true), .extraHeaders(["id": myDatabaseID])])
self.socket = manager?.defaultSocket
self.socket?.connect()
//self.socket?.on events go here
}
On the client side, my connection code looks like:
const io = require('socket.io')(http, {
pingTimeout: 10000
});
io.on('connection', onConnection);
function onConnection(socket){
let headerDatabaseID = socket.handshake.headers.id
//in the for loop below, I essentially disconnect any socket that has the same database ID as the one that just connected (in this case, when a client is in a room with other clients,
//and then he/she switches from WiFi to LTE, the client reconnects to the socket server and this removes the old connection from the room it was in)
for (let [id, connectedSocket] of io.sockets.sockets) {
if (connectedSocket.databaseID == headerDatabaseID && socket.id != id) {
connectedSocket.disconnect()
break
}
}
//socket.on() events here
}
My issue is this--how do I go about reconnecting the client when it makes the passive network switch (WiFi -> LTE or vice versa)? I thought that just adding .reconnects(true) would work but for some reason, it's not...
Please let me know if I can be more detailed/helpful or if you'd like to see other codes.
I believe the solution to you problem can be either simple or complex; that depends on your requirements. I assume that each chat room has its own ID.
If you store that ID in memory on the device, when the user reconnects, you can have the socket reconnect to the room ID you had last and they will re-join that room. This is insecure.
If rooms are protected and not public, someone may be able to connect to a room that they are not allowed in if they know/can guess the room ID. To solve that problem, you'd need to implement some sort of authentication or server side database that keep keep track of that sort of stuff.
Considering the behavior varies based on whether the handoff is manual or passive it sounds like the issue is on the iOS client. I notice that you are using sockets - it seems to be some sort of custom sockets package, right? Is there a reason for using this? URLSession is a higher level implementation and it manages things like handoff.
There is something called Wifi assist, developed by apple, to manage handoff. It is part of the OS and manages this internally. According to apple: "Using URLSession and the Network framework already gives us the new WiFi assist benefits.". This was released in iOS 9, in Sept 2015.
But if you are using some other kind of sockets, whatever this "socketIOClient" is - especially packages developed prior to Sept 2015, you are probably bypassing Wifi assist. The latest version of SocketIO client I see was written in 2015 and it appears support for this package was discontinued when iOS 9 came out.
When the user manually changes the connection this is manually prompting the OS to tear down & reestablish the connection, whereas with passive tradeoff it normally relies on this Wifi Assist.
You could try to programmatically tear down & reestablish the connection when you detect that a passive handoff has occurred, but I wouldn't recommend this... for starters, it will make your code much messier. It will probably degrade the user experience. But worse, this may not be the only problem you run into using this outdated socketIO package. There's really no telling what kind of maintenance problems you will wind up with. Better to just refactor your code to use the up to date networking mechanisms provided by iOS.
If .reconnects(true) isn't working, you can try to manually take care of the problem with Apple's Reachability. This may make it easier - it's the Reachability functionality "re-written in Swift with closures."
In your case, you might use it as such:
let reachability = try! Reachability()
reachability.whenReachable = { reachability in
if reachability.connection == .wifi {
print("Reachable via WiFi")
self.socket.disconnect();
establishConnection() //this is your method defined in the question
} else {
print("Reachable via Cellular")
self.socket.disconnect();
establishConnection() //this is your method defined in the question
}
}
reachability.whenUnreachable = { _ in
print("Not reachable")
}
do {
try reachability.startNotifier()
} catch {
print("Unable to start notifier")
}

SignalR and/or timer issues since Chrome 88

We have an ASP.Net WebForms application that uses SignalR (v2.4.1) to do some bi-directional communications between server and client. It's worked fine for years: connections are stable, hundreds of users use it, etc.
However, we've started to get sporadic reports of connection problems from across our client base, all reporting the same thing: if the browser (Chrome) session goes idle for more than 5 minutes, the connection drops in the background. All timers in the page stop being run regularly, which (amongst other things) stops "keepalives" stop being sent, and eventually the connection fails with the client-side error:
The client has been inactive since <date> and it has exceeded the inactivity timeout of 50000 ms. Stopping the connection.
Standard procedure after this would be to automatically restart the connection, but this doesn't do anything. If/when the user reactivates the page (e.g. by switching to the tab), everything starts to spring back into life, albeit with a closed SignalR connection.
After much investigation, it seems that we're being impacted by this change introduced in Chrome v88, where timers (setTimeouts) are severely restricted if
The page has been hidden for more than 5 minutes
The timer has been "chained" 5 or more times - I'm assuming this is similar to recursion, where the timer calls itself.
Page has been "silent" for 30 seconds
The 5 minutes/30 seconds condition fits with the reports we're getting. However, we're running pretty basic Javascript on our page: there are only two uses of setTimeout in our own code, neither of which could ever "chain" (recurse) onto themselves. We also cannot replicate the issue: it's happened to us in testing, but we can't make it happen reliably. Disabling this feature via chrome://flags/#intensive-wake-up-throttling seems to mitigate the issue - but of course, we can't make this a requirement to use our site.
The only other Javascript running on the site is jquery.signalR-2.4.1.js, and from the SignalR source, there are lots of setTimeouts in there. Could SignalR be impacted by this change in Chrome; perhaps when it tries to silently reconnect after a temporary network issue or some other unpredictable event?
If not, is there any way, in any browser or IDE, to track which timers have been launched (and, more importantly, "chained"), so we can see what could be triggering this restriction?
We're as well facing issues with our signalR (WebSockets as transport). We're not able to reproduce it in our lab. The HAR files of our customer and extended logging provided us only the information that the client "consuming only after following interesting groups" is not sending pings within the default 30 seconds needed to keep the connection. Therefore the server closes the connection. We added logs in the signalR client library and only saw the ping timer not being hit on time. No error, no nothing. (Client is JavaScript and the issue occurred on customer site in chrome 87 (throttling was implemented there already for half of the chrome users - https://support.google.com/chrome/a/answer/7679408#87))
And the world is slowly getting aware of "an issue": https://github.com/SignalR/SignalR/issues/4536
Our quick help for our customers will be to create an ET with a manual broadcast ping-pong mechanism from the server site and each client will have to answer. Avoiding being dependent on the JavaScript ping in the signalR library until a "better" solution or fix is provided.
As a workaround, javascript library that does the ping can be modified, to slightly change the way that it uses the timers. One of the conditions for intensive throttling is that the setTimeout()/setInterval() chain count is 5+. This can be avoided for recurring calls, by using a web worker. The main thread can post a dummy message to the web worker, which does nothing other than posting a dummy message back to the main thread. The subsequent setTimeout() call can be made on the message event from the web worker.
i.e.,
main_thread_ping_function :- doPing() -> post_CallMeBack_ToWebWorker()
web_worker :- onmessage -> post_CallingYouBack_ToMainThread()
main_thread :- web_worker.onmessage -> setTimeout(main_thread_ping_function, timeoutValue)
Since the setTimeout() is called on a message from web worker, rather than from the setTimout() execution flow, the chain length remains one, and thus no intensive throttling would be done by chrome 88+.
Note that, chained setTimeout() calls in a web worker are not throttled by chrome at the moment, and thus defining the timer functionality inside a web worker, and acting on the messages(to perform ping) from web worker, too solves the problem. However, if chrome developers decide to throttle the timers in web workers too, in the future, it gets broken again.
A utility(similar to java scheduled executor) which allows scheduling of callbacks using web workers, to avoid throttling, by context switching:
class NonThrottledScheduledExecutor {
constructor(callbackFn, initialDelay, delay) {
this.running = false;
this.callback = callbackFn;
this.initialDelay = initialDelay;
this.delay = delay;
};
start() {
if (this.running) {
return;
}
this.running = true;
// Code in worker.
let workerFunction = "onmessage = function(e) { postMessage('fireTimer'); }";
this.worker = new Worker(URL.createObjectURL(new Blob([workerFunction], {
type: 'text/javascript'
})));
// On a message from worker, schedule the next round.
this.worker.onmessage = (e) => setTimeout(this.fireTimerNow.bind(this), this.delay);
// Start the first round.
setTimeout(this.fireTimerNow.bind(this), this.initialDelay);
};
fireTimerNow() {
if (this.running) {
this.callback();
// dummy message to be posted to web worker.
this.worker.postMessage('callBackNow');
}
};
stop() {
if (this.running) {
this.running = false;
this.worker.terminate();
this.worker = undefined;
}
};
};
<button onclick="startExecutor()">Start Executor</button>
<button onclick="stopExecutor()">Stop Executor</button>
<div id="op"></div>
<script>
var executor;
function startExecutor() {
if (typeof(executor) == 'undefined') {
// Schedules execution of 'doThis' function every 2seconds, after an intial delay of 1 sec
executor = new NonThrottledScheduledExecutor(doThis, 1000, 2000);
executor.start();
console.log("Started scheduled executor");
}
}
function stopExecutor() {
if (typeof(executor) != 'undefined') {
executor.stop();
executor = undefined;
document.getElementById("op").innerHTML = "Executor stopped at " + l;
}
}
var l = 0;
function doThis() {
l = l + 1;
document.getElementById("op").innerHTML = "Executor running... I will run even when the my window is hidden.. counter: " + l;
}
</script>
Microsoft have released SignalR 2.4.2, which should address the issue natively and avoid the need for any manual workarounds.
Nuget package available here, and the list of fixed issues is here
I know that it does not solve the problem altogether with chrome, however, the new edge that uses chromium engine has added a few new settings to govern the timeouts (since it was affected too by the change). There is a new whitelisting option that gives at least the power to the users to decide which pages are excluded from this behavior. I honestly do believe that these setting will be added by google sooner or later. Until then we recommend our customers to switch to edge if they are affected.
You can find it in settings\system:

How to get the latency time for a one way trip from client to server

I was wondering if there was a way to get the time (in ms) between the client sending a message to the server and the server receiving that message
I cannot compare the time in milliseconds with the server and client using Date.now() because every device might be off by a few seconds.
I can find the time for a two way trip, logging the time when I send a message and logging the time again when I receive a message in return from the server. However, The time it takes for a message to get from the client to the server may not be the same as it is for the message to get from the server to the client on a two way trip. So I cant just simply divide this time by 2.
Any suggestions on how I can find this time or at least the difference between Date.now() on the client and the server?
Thanks in advance.
You can achieve this if you first synchronize the clocks of both your server and client using NTP. This requires access to an external server, however you can configure NTP to be installed on your server as well (see ntpd)
There are several modules that implement NTP in node: node-ntp-client or sntp
Here's an example with node-ntp-client:
var ntpClient = require('ntp-client');
var clientOffset = 0;
ntpClient.getNetworkTime("pool.ntp.org", 123, function(err, date) {
if(err) {
console.error(err);
return;
}
clientOffset = Date.now() - date;
});
When sending data to the server, send the timestamp as well:
var clientTimestamp = Date.now() - clientOffset
Server would have its own offset. When receiving the package, it can calculate latency using:
var latency = Date.now() - serverOffset - clientTimestamp;
I was wondering if there was a way to get the time (in ms) between the client sending a message to the server and the server receiving that message
No, there is not. At least, not without a common clock reference.
If I were to mail you a letter, you know what day you received the letter on but you don't know when it was sent. Therefore, you have no idea how long it took the post office to route and deliver the letter to you.
One possible solution is for me to date the letter. When you receive it, you can compare the received date to the date I sent it and determine how many days it was in transit. However, what if I wrote down the wrong date? Suppose I thought it was Friday when it was really Wednesday. Then, you can't accurately determine when it was sent.
Changing this scale back to computers, we'll have to use our realtime clock (RTC) to timestamp the packet we send. Even with reasonable accuracy, our RTCs might be set a minute off from each other. I could send you a packet at 01:23:01.000Z my time, and you might receive it 10 milliseconds later... at 01:23:55.00Z your time and calculate that it took 54 seconds to reach you!
Even if you synchronize with NTP, over the internet, that's potentially 10s to 100s of milliseconds off.
The way very accurate clock synchronization is usually done is via GPS receivers, which by their nature serve as an extremely accurate clock source. If you and I were both very accurately sychronized to GPS receivers, I could send you a packet and you could calculate how long it took.
This is generally impractical, which is why when we ping stuff, we use round-trip time.

How to reduce time difference between clients receiving data in socket.io?

A node.js project with modules socket.io and express.
Now each client has a canvas, which runs animations on it. When server emit the initiate parameter, the animation can start.
Now the problem is, there is a Time Gap between clients when their animations start. The longer the animation runs, the more obvious the gap would be. The position of the figures would become really different. But what i want is everybody see the same thing on their screen。
Here's how the server deliver the data:
socket.broadcast.emit('init', initData);
socket.emit('init', initData);
The animation function is in the client, it starts when receiving the initiate data from the server.
I'm not sure if it's because the time receiving these data is different in each client.
So how to reduce this gap?
Many thanks.
I think you should try the following: make sure (using onLoad events and collecting that events on server with socket.io) that every clients downloaded animation, and then send signal to start it.
here is a simple formula and routine that works with socket.io, php or anything really.
I used it to fade in a live video stream 10 seconds before it aired. Given the inherent lag and device performance patterns, and wrong time-zones, you can only expect to get about 30ms of precision forward or backward, but to most observers, it all happens "at the same time".
here is a simulation of a server that's about two minutes behind a simulated client (you), and the server wants the client to show a popup in 1.2 seconds:
//the first two vars should come from the server:
var serverWant=+new Date() - 123456 + 1200; // what server time is the event ?
var serverTime=+new Date() - 123456; // what server time is now ?
//the rest happens in your normal event using available info:
var clientTime=+new Date(); // what client time is now ?
var off= clientTime - serverTime; // how far off is the client from the server?
var clientWant= serverWant + off; // what client time is the event ?
var waitFor = clientWant - +new Date(); // how many millis to time out for until event ?
setTimeout(function(){ alert( 'SNAP!' );}, waitFor);
how reliable is this? try changing both "- 123456"s to "+ 12345"s and see if the popup still waits 1.2 seconds to fire, despite not using Math.abs anywhere in the calculation...
in socket.io, you could send the server time and scheduled time to the client for computation in a pre-event:
socket.broadcast.emit('pre-init', {
serverTime: +new Date(),
serverWant: +new Date() + 1200
});
and then use those values and the above math to schedule the animation in a few moments or a few hours as needed, on-demand (yours) to the split second.
You need Dead Reckoning technique in order to simulate client side state as close to real state on server as possible.
You might send state packages to clients periodically, for example every 200ms (5 times a second), and on client side extrapolate from this data.
Additionally to this, you have to remember about different latency for different clients. So as you want to keep same state there is generally two approaches - interpolation (use last known and one before data), or extrapolation (use last known and predict in future based on own latency).
Extrapolation suits better for real-time interactive stuff, but will have problems with error correction - when client will do wrong prediction (object suddenly stopped but based on delay client predicted it still moved).
Interpolation would make everything pretty much delayed and in a past, but will not suffer from errors as there is no predictions. The drawback of this as you need to wait before interpolating amount of time equal to slowest latency user. This means that slower user will force everyone to be slowed down as well.

Need simple data push to browser using node.js

On the server side, I use node.js to do some distributed asynchronous ping-pong. I now need to display the results as a real-time chart in a client browser. To keep things simple, I am presently using the image-based Google chart URL and restricting the amount of data to be plotted. Eventually this client-side display piece will be rich & interactive.
I understand that one of the ways for my server to push the data out to the browser is Comet. I expect there must be a corresponding socket-something on the browser side, so the two should go together.
Q1: For prototyping: what is the simplest way for me to push string data from node.js to my Firefox 3.6.10 browser? String updates less than 1KB once per second.
Q2: For production: any recommendations for an approach that will work across browsers, including mobile devices? Binary updates order of 100KB per second, no images or video.
I'd really recommend taking a look at http://socket.io/ for Node.js. It works on mobile devices, and supports multiple methods for the Comet effect that you desire, utilizing the best option available to the browser.
It's pretty dead simple too, although it does lack channels, but it's an easy workaround using socket.broadcast(msg, [array containing every user except those 'subscribed'])
Every two seconds server generates a random number r1 in [0,100], then messages client to draw a piechart with r1 and r2=100-r1. Yet to implement the broadcast suggested for multiple clients. Any other suggestions for improvements welcome.
Server side (in coffeescript):
http = require('http')
io = require('socket.io')
server = http.createServer( )
server.listen(8000)
socket = io.listen(server)
myrand = (client) -> setInterval( ->
r1 = Math.floor(Math.random()*101)
r2 = 100-r1
client.send(String(r1) + ',' + String(r2))
, 2000)
socket.on('connection', (client) -> myrand(client))
Client side (index.html with javascript):
<h1>My socket client</h1>
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<div id="piechart">
Hello World
</div>
<script>
socket = new io.Socket('localhost:8000');
socket.connect();
socket.on('message', function(data){
url = 'http://chart.apis.google.com/chart?cht=p3&chs=250x100&chd=t:' + data + '&chl=Hello|World';
document.getElementById('piechart').innerHTML = "<img src="+ url + "></img>";
});
</script>

Categories