【问题标题】:Synchronize time in javascript with a good precision (>0.5s) (NTP-like)以良好的精度(> 0.5s)在javascript中同步时间(类似NTP)
【发布时间】:2011-12-12 17:20:02
【问题描述】:

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

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

更新: 它甚至可以使用移动连接。 3G 连接本身的往返时间约为 0.5 秒并不少见(例如在意大利),因此算法必须是稳健的。

【问题讨论】:

    标签: javascript ajax time ntp


    【解决方案1】:

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

    【讨论】:

    • 您在 PHP 中的格式看起来不正确:microtime() 不带参数返回“0.uuuuuu ssssssssss”形式的字符串。我宁愿echo round(microtime(true)*1000)
    • 另外几个更正:如果 PHP 代码需要 POST,JavaScript 应该request.open("POST", ...)。它也应该request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");,否则 PHP 可能会感到困惑。哦,顺便说一句,PHP 代码不需要提供两个时间戳(接收和传输),因为它们的差异不太可能超过一毫秒。也不需要原始时间戳来进行往返:JavaScript 可以记住它。因此,PHP 代码只需echo round(microtime(true) * 1000);,不需要更多。
    • 我认为会有2个问题,如果我错了,请纠正我:1)ICMP时间戳使用ICMP作为传输协议。该算法使用 TCP,所以我认为 TCP 的 3 次握手会引入显着且不可预测的延迟(并且 oneway 不是往返/2)。如果该 POST 涉及“CORS 预检”以获得跨域 ajax 的授权,则情况更糟。也许利用 HTTP 持久连接可以成为提高准确性的可行解决方案? (例如,打开一个虚拟请求来初始设置连接而不是使用建议的算法?)
    • 2) 服务器端sync.php 中的$receive 不代表收到数据包的时间。它仅在服务器没有负载时才有效。在常见情况下,Web 服务器将请求放入队列中,并且 php 进程池以 FIFO 逻辑处理请求。在此实现中,无法区分接收数据包的时间和 php 进程处理数据包的时间。以及如何识别响应是有效还是无效? (例如,由于大量请求引入了延迟?)
    • @micred:如果进行跨域 AJAX(实际上甚至是域内),我会使用 GET 而不是 POST。正如我之前评论的那样,原始时间戳不需要进行往返,因此 POST 没有任何好处。此外,如果请求发送到传递 JavaScript 的同一服务器,则此连接很可能会被重用。
    【解决方案2】:

    如果你只是想在多台电脑上同步时钟,NTP 自己推荐setting up your own timeserver

    服务器端点

    在您的服务器上设置一个 api 端点 即

    http://localhost:3000/api/time
    

    返回:

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

    这将取决于您使用的后端语言。

    客户代码

    给定这样一个端点,我扔的这个 JS sn-p 会

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

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

    【讨论】:

    • 这很有用,谢谢。使用中位数而不是均值来拒绝异常值也可能有意义。
    猜你喜欢
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 2012-08-04
    • 1970-01-01
    • 2013-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多