2011-12-12 86 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