75 votes

La meilleure façon de synchroniser l'horloge javascript côté client avec la date du serveur

J'ai pour tâche d'afficher une horloge numérique (avec une précision de quelques minutes) sur une page HTML dans un fuseau horaire fixe (MSK ou MSD - en fonction de la date du jour). J'aimerais éviter de dépendre de l'horloge du système du client, donc une certaine synchronisation avec le serveur est nécessaire. Le serveur HTTP envoie un en-tête Date dans chaque réponse, ce qui nous permet d'envoyer une requête AJAX GET ou HEAD à n'importe quelle URL de notre site pour obtenir la date du serveur, calculer la différence avec la date du client et l'utiliser lors de la mise à jour de l'horloge avec setTimeout(). D'autres problèmes subsistent : le changement de fuseau horaire pour les réglages de la lumière du jour, la prise en compte de la latence pour les connexions très lentes.

Une idée pour réaliser cette tâche de la manière la plus simple ? Je préférerais la résoudre sans programmation côté serveur.

49voto

Mehdi Yeganeh Points 666

Vous pouvez calculer l'heure exacte avec NTP (Network Time Protocol) dans votre code,

Je vais essayer de vous expliquer :

  1. Nous avons l'heure du client lors de l'envoi de la demande (par exemple 4/3/2012 13:56:10.123).
  2. Vous envoyez ClientTime au serveur
  3. Nous avons Durée de l'aller-retour pour la demande, je l'ai appelée Heure de la demande (par exemple : Il faut 5 secondes)
  4. Dans le serveur, nous calculons la différence de temps entre le serveur et le client (par exemple) : Il ServerTime - ClientTime = ServerClientDifferenceTimeWithRequestTime), il faut maintenant que cette différence comprenne le temps d'aller-retour de la demande à l'étape 3, puis il faut retirer le temps d'aller-retour de la différence.
  5. Serveur Envoi d'une réponse comprenant ServerClientDifferenceTimeWithRequestTime et ServerTime
  6. Nous avons Durée de l'aller-retour pour la réponse, je l'ai appelée Temps de réponse (par exemple : Il faut 3 secondes)
  7. Dans le cas du client, nous calculons à nouveau la différence de temps entre le serveur et le client (par exemple) : Il ServerTime - ClientTime = ServerClientDifferenceTimeWithResponseTime), encore une fois : vous devriez maintenant cette différence y compris le temps de réponse aller-retour à l'étape 6.
  8. Nous avons maintenant du temps dans le client
  9. Vous devez calculer des équations simples dans le client :

X(SyncedTime) \= Now + (ServerClientDifferenceTimeWithRequestTime - RquestTime)

X(SyncedTime) \= Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

Now - ClientTime = RquestTime + ResponseTime =>

Now - (ServerClientDiffRq - RquestTime) = Now - (ServerClientDiffRs - ResponseTime)

si vous avez résolu le problème, vous avez trouvé ceci :

ResponseTime = (ServerClientDifferenceTimeWithRequestTime - Now + ClientTime + - ServerClientDifferenceTimeWithResponseTime )/2

et vous pouvez ensuite trouver l'heure synchronisée ou l'heure du serveur dans le client avec cette équation :

X(SyncedTime) \= Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

Je montre un code simple, mais si vous voulez l'écrire, n'oubliez pas d'utiliser les fonctions de date et d'heure UTC...

Côté serveur (par exemple php, c#) :

PHP :

header('Content-Type: application/json; charset=utf-8');
$clientTime = $_GET["ct"] * 1; //for php 5.2.1 or up: (float)$_GET["ct"];
$serverTimestamp = round(microtime(true)*1000); // (new DateTime())->getTimestamp();
$serverClientRequestDiffTime = $serverTimestamp - $clientTime;
echo "{\"diff\":$serverClientRequestDiffTime,\"serverTimestamp\":$serverTimestamp}";

C# :

long clientTime = long.Parse(Request.Form["ct"]);
long serverTimestamp = (DateTime.Now.Ticks-(new DateTime(1970,1,1) - DateTime.MinValue).Ticks) / 10000;
long serverClientRequestDiffTime = serverTimestamp - clientTime;
Response.Write("{\"diff\":"+serverClientRequestDiffTime+",\"serverTimestamp\":"+serverTimestamp+"}");

Côté client (Javascript avec Jquery ):

var clientTimestamp = (new Date()).valueOf();
$.getJSON('http://yourhost.com/getdatetimejson/?ct='+clientTimestamp, function( data ) {
    var nowTimeStamp = (new Date()).valueOf();
    var serverClientRequestDiffTime = data.diff;
    var serverTimestamp = data.serverTimestamp;
    var serverClientResponseDiffTime = nowTimeStamp - serverTimestamp;
    var responseTime = (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2

    var syncedServerTime = new Date((new Date()).valueOf() + (serverClientResponseDiffTime - responseTime));
    alert(syncedServerTime);
});

3 votes

fr.wikipedia.org/wiki/Network_Time_Protocol Il s'agit du protocole ntp, pour lequel j'ai utilisé l'algorithme de synchronisation d'horloge.

1 votes

Il utilise le même algorithme de base que NTP pour calculer la différence entre l'heure du serveur et celle du client, ce qui est très astucieux. Voir la page cette partie de la page NTP.

2 votes

J'ai corrigé certains bugs, mais cela ne fonctionne pas. Si l'horloge du client est désactivée, cela ne donne pas l'heure correcte telle qu'elle est perçue par le serveur.

40voto

Fedearne Points 1731

Ces deux fonctions Javascript devraient faire l'affaire.

var offset = 0;
function calcOffset() {
    var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    xmlhttp.open("GET", "http://stackoverflow.com/", false);
    xmlhttp.send();

    var dateStr = xmlhttp.getResponseHeader('Date');
    var serverTimeMillisGMT = Date.parse(new Date(Date.parse(dateStr)).toUTCString());
    var localMillisUTC = Date.parse(new Date().toUTCString());

    offset = serverTimeMillisGMT -  localMillisUTC;
}

function getServerTime() {
    var date = new Date();

    date.setTime(date.getTime() + offset);

    return date;
}

EDIT : supprimé ".replace(/^(. )[ \s\S ] /,"$1")".

calcOffset() calcule le décalage par rapport à l'heure du serveur et compense pour GMT/UTC.

getServerTime() pour obtenir le décalage de l'heure locale par rapport aux serveurs, en utilisant le fuseau horaire local.

Si calcOffset() prend du temps à s'exécuter, vous risquez de perdre quelques secondes de précision. Le temps d'exécution pourrait peut-être être pris en compte....

Si vous craignez que le décalage calculé soit erroné lorsque l'heure locale ou l'heure du serveur passe à l'heure d'été ou à l'heure d'hiver, vous pouvez recalculer un peu après chaque heure d'horloge, le système compensera les changements de l'heure d'été. Il peut être nécessaire d'attendre que l'horloge locale et celle du serveur aient passé l'heure.

L'exemple ne fonctionne que dans IE à cause de "Msxml2.XMLHTTP" je pense.....

0 votes

Ce que cette étrange expression est censée faire : .replace(/^(.*)[\s\S]*/,"$1") ?

0 votes

J'ai en fait copié cette partie d'une vieille source. L'auteur ne se souvient pas de ce qu'elle fait et elle ne semble même pas être une regex valide..... Désolé pour cela....

9 votes

Le changement de méthode de "GET" à "HEAD" fera que le serveur http ne renverra pas le corps du texte. Cela peut accélérer la requête.

31voto

Aktau Points 562

J'ai trouvé que l'algorithme de @mehdi-yeganeh ci-dessus ne donnait pas de résultats utiles mais l'idée est bonne : utiliser l'algorithme NTP (ou au moins une version faible de celui-ci) pour synchroniser les horloges du serveur et du client.

Il s'agit de mon implémentation finale, qui utilise les en-têtes de réponse du serveur s'ils sont disponibles pour plus de précision (corrigez-moi si je me trompe, mes propres tests indiquent que c'est assez précis).

côté navigateur (javascript) :

// the NTP algorithm
// t0 is the client's timestamp of the request packet transmission,
// t1 is the server's timestamp of the request packet reception,
// t2 is the server's timestamp of the response packet transmission and
// t3 is the client's timestamp of the response packet reception.
function ntp(t0, t1, t2, t3) {
    return {
        roundtripdelay: (t3 - t0) - (t2 - t1),
        offset: ((t1 - t0) + (t2 - t3)) / 2
    };
}

// calculate the difference in seconds between the client and server clocks, use
// the NTP algorithm, see: http://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm
var t0 = (new Date()).valueOf();

$.ajax({
    url: '/ntp',
    success: function(servertime, text, resp) {
        // NOTE: t2 isn't entirely accurate because we're assuming that the server spends 0ms on processing.
        // (t1 isn't accurate either, as there's bound to have been some processing before that, but we can't avoid that)
        var t1 = servertime,
            t2 = servertime,
            t3 = (new Date()).valueOf();

        // we can get a more accurate version of t2 if the server's response
        // contains a Date header, which it generally will.
        // EDIT: as @Ariel rightly notes, the HTTP Date header only has 
        // second resolution, thus using it will actually make the calculated
        // result worse. For higher accuracy, one would thus have to 
        // return an extra header with a higher-resolution time. This 
        // could be done with nginx for example:
        // http://nginx.org/en/docs/http/ngx_http_core_module.html
        // var date = resp.getResponseHeader("Date");
        // if (date) {
        //     t2 = (new Date(date)).valueOf();
        // }

        var c = ntp(t0, t1, t2, t3);

        // log the calculated value rtt and time driff so we can manually verify if they make sense
        console.log("NTP delay:", c.roundtripdelay, "NTP offset:", c.offset, "corrected: ", (new Date(t3 + c.offset)));
    }
});

côté serveur (php, mais cela peut être n'importe quoi) :

Votre serveur à la route 'GET /ntp' devrait renvoyer quelque chose comme :

echo (string) round(microtime(true) * 1000);

Si vous avez un PHP >5.4, vous pouvez sauvegarder un appel à microtime() et le rendre un peu plus précis avec :

echo (string) round($_SERVER['REQUEST_TIME_FLOAT'] * 1000);

NOTE

Cette façon de faire peut être considérée comme une sorte de ghetto, il y a d'autres réponses de Stack Overflow qui pourraient vous guider vers une meilleure solution :

0 votes

La correction t2 ne fonctionne pas bien parce qu'elle n'a qu'une résolution de seconde au lieu de milliseconde comme les autres. En fait, elle aggrave la précision.

0 votes

Merci de m'avoir prévenu. Je n'avais même pas pensé au problème de la résolution temporelle. Je le signalerai dans ma réponse et prendrai note de votre remarque.

0 votes

+1. Je pense en fait que cela devrait être la réponse acceptée - dans ma mise en œuvre, chaque réponse du serveur contient des en-têtes HTTP personnalisés avec t1 et t2 (avec une précision de ms), le reste est exactement comme vous l'avez écrit.

12voto

barbuza Points 614

Si vous utilisez ajax, vous devez vous souvenir du temps client entre readyState==2 et readyState==3, car le temps serveur sera fixé quelque part entre le temps de réception de la requête et le temps de préparation de la réponse.

0 votes

Un exemple avec jquery ou un autre framework JS cachant une différence dans l'implémentation AJAX ?

0 votes

Je n'ai pas fourni d'exemple utilisant jquery/mootools/prototype parce qu'ils cachent tous le travail avec l'objet xmlhttprequest natif du navigateur et ne fournissent pas d'interface pour gérer les événements de changement d'état.

0voto

snicker Points 3168

Je ne demanderais la mise à jour au serveur que toutes les 30 secondes environ, si vous avez besoin d'une précision à la minute près. Ne vous fiez pas du tout à l'heure du client, mais utilisez l'horloge de son système pour maintenir l'exactitude de l'horloge entre les mises à jour. Je pense que vous avez répondu à votre propre question ?

Il serait utile de mieux comprendre ce que vous essayez de faire.

Si vous souhaitez simplement qu'une horloge affiche l'heure sur le serveur, qui est ensuite ajustée à un certain fuseau horaire, faites-le côté client avec des décalages. Gérez l'heure d'été dans les fuseaux horaires où elle s'applique en utilisant également la date que vous recevez du serveur. Si vous voulez déterminer la latence, vous aurez probablement besoin d'un petit script sur le serveur pour calculer la différence. Mais comme ci-dessus, cela aiderait à mieux comprendre le problème. Si la précision n'est que de l'ordre de la minute, la latence semble moins critique.

3 votes

Une seule synchronisation suffit pour obtenir la différence. La question portait sur la gestion correcte des fuseaux horaires et de la latence comptable, ou sur une meilleure façon de résoudre le problème.

0 votes

Dans ce cas, la question n'est pas très claire, comme l'indique doublement le commentateur qui pense que l'auteur de la question voulait changer l'horloge du client.

1 votes

Je pense que l'expression "afficher l'horloge numérique" signifie un balisage HTML avec une mise à jour par javascript.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X