以高精度(> 0.5s)同步javascript中的时间(类似NTP)

时间:2011-12-12 17:20:02

标签: javascript ajax time ntp

我正在寻找一种方法,以精确的方式同步客户端之间的时间(比如说至少0.5秒)。

由于精度不佳(一秒或更短),我排除了使用jsontime或利用服务器响应头中的时间戳。

更新: 它应该适用于移动连接。 3G连接本身的往返时间约为0.5秒并不是不常见的(例如在意大利),因此算法必须是健壮的。

2 个答案:

答案 0 :(得分: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个单位。
  5. 使用这些数据,我们可以计算:

    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时间。但是,此等式的准确性取决于您计算单程时间的准确程度。如果发送和接收消息的实际时间不相等或大致相等,则需要找到其他方法来计算单向时间。但是,为了您的目的,这应该足够了。

答案 1 :(得分:3)

如果您只想在多台计算机上同步时钟,NTP自己会推荐setting up your own timeserver

服务器端点

在服务器上设置api端点 即。

http://localhost:3000/api/time

返回:

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

如何完成此操作取决于您正在使用的后端语言。

客户端代码

鉴于这样的端点,我把这个JS片段放在一起

  1. 轮询您的服务器端点10次。
  2. 通过消除往返时间的一半来尝试弥补延迟。
  3. 报告服务器和客户端之间的平均偏移量。
  4. &#13;
    &#13;
    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();
    &#13;
    &#13;
    &#13;

    您可以使用此计算的偏移量(只需将其添加到当地时间),以确定常见的&#34;通用&#34;来自每个客户的背景的时间。