2011-12-12 7 views
12

Sto cercando un modo per sincronizzare l'ora tra i client con una buona precisione (diciamo almeno 0,5 secondi).Sincronizza l'ora in javascript con una buona precisione (> 0,5 s) (simile a NTP)

Escludo l'utilizzo di jsontime o l'utilizzo del timestamp nelle intestazioni di risposta del server a causa di una scarsa precisione (un secondo o forse meno).

AGGIORNAMENTO: Dovrebbe funzionare anche con le connessioni mobili. Non è raro (per esempio qui in Italia) che le connessioni 3G abbiano un tempo di andata e ritorno intorno agli 0,5, quindi l'algoritmo deve essere robusto.

risposta

16

Resort al buon vecchio schema di messaggio ICMP Timestamp. È abbastanza semplice da implementare in JavaScript e PHP.

Ecco un'implementazione di questo schema utilizzando JavaScript e PHP:

// browser.js 

var request = new XMLHttpRequest(); 
request.onreadystatechange = readystatechangehandler; 
request.open("POST", "http://www.example.com/sync.php", true); 
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 
request.send("original=" + (new Date).getTime()); 

function readystatechangehandler() { 
    var returned = (new Date).getTime(); 
    if (request.readyState === 4 && request.status === 200) { 
     var timestamp = request.responseText.split('|'); 
     var original = + timestamp[0]; 
     var receive = + timestamp[1]; 
     var transmit = + timestamp[2]; 
     var sending = receive - original; 
     var receiving = returned - transmit; 
     var roundtrip = sending + receiving; 
     var oneway = roundtrip/2; 
     var difference = sending - oneway; // this is what you want 
     // so the server time will be client time + difference 
    } 
} 

Ora, per il codice sync.php:

<?php 
    $receive = round(microtime(true) * 1000); 
    echo $_POST["original"] . '|'; 
    echo $receive . '|'; 
    echo round(microtime(true) * 1000); 
?> 

Non ho ancora testato il codice di cui sopra, ma dovrebbe funzionare.

Nota: Il metodo seguente calcolerà con precisione la differenza di tempo tra il client e il server a condizione che il tempo effettivo per inviare e ricevere messaggi è lo stesso o circa lo stesso. Si consideri il seguente scenario:

Time Client Server 
-------- -------- -------- 
Original  0  2 
Receive   3  5 
Transmit  4  6 
Returned  7  9 
  1. Come si può vedere, gli orologi client e server sono 2 unità al largo di sincronizzazione. Quindi quando il client invia la richiesta di data/ora, registra l'ora originale come 0.
  2. Il server riceve la richiesta 3 unità più tardi, ma registra il tempo di ricezione come 5 unità perché è di 2 unità avanti.
  3. Quindi trasmette la risposta data/ora una unità dopo e registra il tempo di trasmissione come 6 unità.
  4. Il client riceve la risposta dopo 3 unità (cioè a 9 unità in base al server). Tuttavia, dal momento che sono 2 unità dietro il server, registra il tempo restituito come 7 unità.

Utilizzando questi dati, possiamo calcolare:

Sending = Receive - Original = 5 - 0 = 5 
Receiving = Returned - Transmit = 7 - 6 = 1 
Roundtrip = Sending + Receiving = 5 + 1 = 6 

Come si può vedere da sopra, i tempi di invio e ricezione sono calcolati in modo non corretto a seconda di quanto il client e il server sono fuori sincronia. Tuttavia, il tempo di andata e ritorno sarà sempre corretto perché stiamo aggiungendo prima due unità (ricevi + originale) e quindi sottraendo due unità (restituite - trasmetti).

Se assumiamo che il tempo unidirezionale è sempre metà del tempo di andata e ritorno (cioè il tempo per trasmettere il momento di ricevere, allora si può facilmente calcolare la differenza di tempo come segue):

Oneway = Roundtrip/2 = 6/2 = 3 
Difference = Sending - Oneway = 5 - 3 = 2 

Come puoi vedere, abbiamo calcolato con precisione la differenza di tempo come 2 unità. L'equazione per la differenza di orario è sempre sending - oneway. Tuttavia, la precisione di questa equazione dipende da quanto accuratamente si calcola il tempo di percorrenza. Se il tempo effettivo di invio e ricezione dei messaggi non è uguale o approssimativamente uguale, sarà necessario trovare un altro modo per calcolare il tempo di sola andata.Tuttavia, per i tuoi scopi questo dovrebbe essere sufficiente.

+0

Come posso inviare pacchetti ICMP da javascript? Ho visto solo un ActiveX chiamato ActiveSocket per l'invio di pacchetti ICMP, ma non posso usare ActiveX.It funziona su webkit. – micred

+0

Non si inviano pacchetti ICMP. È sufficiente utilizzare lo stesso metodo utilizzato da ICMP per sincronizzare l'ora sul server e sul client. Spero che cancelli il tuo dubbio. Saluti. ;) –

+1

La tua formattazione in PHP sembra errata: 'microtime()' senza parametri restituisce una stringa del modulo "0uuuuu ssssssssss". Preferirei "echo round (microtime (true) * 1000)". –

3

Se si desidera sincronizzare l'orologio su più computer, gli stessi NTP consigliano setting up your own timeserver.

Server Endpoint

Impostare un endpoint API sul server cioè

http://localhost:3000/api/time 

rendimenti:

status: 200, 
body: { time: {{currentTime}} } 

Come questo viene fatto dipende dalla lingua backend che sei utilizzando.

Codice Cliente

Dato un tale endpoint, questo frammento di JS ho buttato insieme sarà

  1. sondaggio server endpoint 10 volte.
  2. Tentativo di compensare la latenza rimuovendo metà del tempo di andata e ritorno.
  3. Segnala lo scostamento medio tra il server e il client.

var offsets = []; 
 
var counter = 0; 
 
var maxTimes = 10; 
 
var beforeTime = null; 
 

 
// get average 
 
var mean = function(array) { 
 
    var sum = 0; 
 
    
 
    array.forEach(function (value) { 
 
    sum += value; 
 
    }); 
 
    
 
    return sum/array.length; 
 
} 
 

 
var getTimeDiff = function() { 
 
    beforeTime = Date.now(); 
 
    $.ajax('/api/time', { 
 
     type: 'GET', 
 
     success: function(response) { 
 
      var now, timeDiff, serverTime, offset; 
 
      counter++; 
 
      
 
      // Get offset 
 
      now = Date.now(); 
 
      timeDiff = (now-beforeTime)/2; 
 
      serverTime = response.data.time-timeDiff; 
 
      offset = now-serverTime; 
 
      
 
      console.log(offset); 
 
      
 
      // Push to array 
 
      offsets.push(offset) 
 
      if (counter < maxTimes) { 
 
      // Repeat 
 
      getTimeDiff(); 
 
      } else { 
 
      var averageOffset = mean(offsets); 
 
      console.log("average offset:" + averageOffset); 
 
      } 
 
     } 
 
    }); 
 
} 
 

 
// populate 'offsets' array and return average offsets 
 
getTimeDiff();

È possibile utilizzare questa calcolato offset (basta aggiungerlo ora locale), per determinare un tempo comune "universale" dal contesto di ogni cliente.

+1

Questo è utile, grazie. Potrebbe anche essere logico utilizzare la mediana anziché il significato per rifiutare i valori anomali. – Groo

Problemi correlati