web-dev-qa-db-fra.com

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

J'ai la tâche d'afficher l'horloge numérique (avec une précision de quelques minutes) sur la page HTML dans un fuseau horaire fixe (MSK ou MSD - selon la date actuelle). Je voudrais éviter de compter sur l'horloge du système client, donc une synchronisation avec le serveur est nécessaire. Le serveur HTTP envoie un en-tête Date dans chaque réponse afin que nous puissions 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 (). Il y a d'autres problèmes: changement de fuseau horaire pour les paramètres de lumière du jour, latence représentant les connexions très lentes.

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

56
n0s

vous devez vous souvenir du temps client entre readyState == 2 et readyState == 3 si vous comptez utiliser ajax, car l'heure du serveur sera définie quelque part entre l'heure de réception de la demande et la préparation de la réponse

9
Victor Kotseruba

Ces deux fonctions Javascript devraient faire l'affaire pour vous.

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 GMT/UTC.

getServerTime () pour obtenir le décalage d'heure locale pour correspondre 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. Peut-être que le temps d'exécution pourrait être pris en compte ...

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

L'exemple ne fonctionne qu'en IE à cause de "Msxml2.XMLHTTP" je pense .....

37
Fedearne

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

j'essaye de t'expliquer:

  1. Nous avons ClientTime on Sending Request (par exemple 4/3/2012 13: 56: 10.123)
  2. Vous envoyez ClientTime au serveur
  3. Nous avons Temps aller-retour pour la demande, je l'ai appelé RequestTime ( par exemple: cela prend 5 secondes)
  4. Dans le serveur, nous calculons le temps de différence entre le serveur et le client (par exemple: It ServerTime - ClientTime = ServerClientDifferenceTimeWithRequestTime), vous devez maintenant cette différence, y compris le temps de demande d'aller-retour à l'étape 3, puis vous devez supprimer le temps d'aller-retour de la différence
  5. Réponse d'envoi du serveur incluant ServerClientDifferenceTimeWithRequestTime et ServerTime
  6. Nous avons Temps d'aller-retour pour la réponse, je l'ai appelé ResponseTime ( par exemple: cela prend 3 secondes)
  7. Dans le client, nous calculons à nouveau le temps de différence entre le serveur et le client (par exemple: It 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 le résolvez, vous avez trouvé ceci:

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

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

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

Je montre du code simple mais quand 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);
});
33
Mehdi Yeganeh

J'ai trouvé que l'algorithme de @ mehdi-yeganeh ci-dessus ne m'a pas donné 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 serveur et client.

Ceci est mon implémentation finale, il utilise les en-têtes de réponse du serveur s'ils sont disponibles pour plus de précision (veuillez me corriger si je me trompe, mes propres tests disent 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 pourrait être n'importe quoi):

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

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

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

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

REMARQUE

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

24
Aktau

Merci à @Mehdi Yeganeh et @Fedearne. J'implémente ma fonction pour utiliser à la fois la logique et ça marche.

https://Gist.github.com/ethaizone/6abb1d437dbe406fbed6

0
EThaizone Jo

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 son horloge système pour garder l'heure exacte entre les mises à jour. Je pense que vous avez répondu à votre propre question?

Il serait utile que nous comprenions mieux ce que vous essayez réellement de faire.

Si vous voulez 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 applicables 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 qu'à la minute près, la latence semble moins critique.

0
snicker