13

我正在寻找一种方法来以良好的精度在客户端之间同步时间(假设至少 0.5 秒)。

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

更新:即使使用移动连接它也应该工作。3G 连接本身具有大约 0.5 秒的往返时间并不少见(例如在意大利),因此算法必须是稳健的。

4

2 回答 2

19

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

于 2011-12-12T17:29:21.437 回答
7

如果您只想在多台计算机上同步时钟,NTP 自己建议设置您自己的时间服务器

服务器端点

在您的服务器上设置一个 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();

您可以使用此计算出的偏移量(只需将其添加到本地时间),从每个客户端的上下文中确定一个常见的“通用”时间。

于 2015-06-12T16:19:18.987 回答