2011-12-12 87 views
12

我正在尋找一種方法來同步客戶端之間的時間,具有很好的精度(假設至少0.5秒)。同步時間在javascript中以很好的精度(> 0.5s)(類似於NTP)

由於精度較差(一秒或更少),我排除了使用jsontime或利用服務器響應頭中的時間戳。

更新: 它應該工作,即使移動連接。這並非罕見(例如在意大利),3G連接本身的往返時間大約爲0.5s,因此算法必須非常強大。

回答

16

度假去舊好ICMP Timestamp消息方案。在JavaScript和PHP中實現相當簡單。

下面是使用JavaScript和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 
    } 
} 

現在的sync.php代碼:

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

我沒有測試上面的代碼,但它應該工作。

注意:如果發送和接收消息的實際時間相同或接近相同,以下方法將準確計算客戶端和服務器之間的時間差。考慮以下情形:

Time Client Server 
-------- -------- -------- 
Original  0  2 
Receive   3  5 
Transmit  4  6 
Returned  7  9 
  1. 正如你所看到的,在客戶端和服務器的時鐘是2個單位了同步。因此,當客戶端發送時間戳請求時,它會將原始時間記錄爲0.
  2. 服務器稍後收到3個請求,但將接收時間記錄爲5個單位,因爲它的前2個單位。
  3. 然後它發送一個單位後的時間戳答覆,並記錄發送時間爲6個單位。
  4. 客戶端收到3個單元后的回覆(即根據服務器爲9個單元)。但是,由於它在服務器後面2個單元,它將返回的時間記錄爲7個單元。

使用這些數據,我們可以計算出:

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

正如你可以看到從上面的發送和接收時間計算不正確,這取決於客戶端和服務器多少錢了同步。然而,往返時間總是正確的,因爲我們首先添加兩個單位(接收+原始),然後減去兩個單位(返回 - 傳輸)。

如果我們假定單向時間總是的往返時間的一半(即,發送時間是接收所述時間,那麼我們可以很容易地計算時間差如下):

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

作爲你可以看到,我們準確地計算出了2個單位的時差。時間差的等式始終爲sending - oneway時間。然而,這個方程的準確性取決於你計算單向時間的準確度。如果發送和接收消息的實際時間不相等或大致相等,則需要找到其他方式來計算單向時間。但是,爲了您的目的,這應該就足夠了。

+0

如何從JavaScript發送ICMP數據包?我只看到一個名爲ActiveSocket的ActiveX來發送ICMP數據包,但我不能使用ActiveX。它在webkit上工作。 – micred

+0

您不發送ICMP數據包。您只需使用ICMP用於同步服務器和客戶端上的時間的相同方法即可。希望清除你的懷疑。乾杯。 ;) –

+1

你在PHP中的格式看起來不正確:不帶參數的'microtime()'返回一個形式爲「0.uuuuuu ssssssssss」的字符串。我寧願'迴響(microtime(true)* 1000)'。 –

3

如果您只是想在多臺電腦上同步時鐘,NTP本身建議使用setting up your own timeserver

服務器端點

建立一個API端點服務器上 即

http://localhost:3000/api/time 

回報:

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

如何做到這一點,將取決於你是後端語言使用。

客戶端代碼

給出了這樣的端點,這個JS代碼片段我扔會

  1. 輪詢服務器端點10倍在一起。
  2. 嘗試通過刪除往返時間的一半來補償等待時間。
  3. 報告服務器和客戶端之間的平均偏移量。

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

您可以使用此計算偏移(只需將其添加到本地時間),確定從每個客戶的情況下常見的「萬能」的時間。

+1

這很有用,謝謝。使用中位數而不是均值來拒絕異常值也可能是有意義的。 – Groo