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

mic*_*red 13 javascript ajax time ntp

我正在寻找一种方法来同步客户端之间的时间与精度(让我们说至少0.5秒).

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

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

Aad*_*hah 17

求助于良好的旧ICMP时间戳消息方案.在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
    }
}
Run Code Online (Sandbox Code Playgroud)

现在sync.php代码:

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

我没有测试上面的代码,但它应该工作.

注意:如果发送和接收消息的实际时间相同或大致相同,则以下方法将准确计算客户端与服务器之间的时间差.请考虑以下情形:

  Time    Client   Server
-------- -------- --------
Original        0        2
Receive         3        5
Transmit        4        6
Returned        7        9
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)

从上面可以看出,发送和接收时间的计算方式不正确,具体取决于客户端和服务器的同步程度.但是,往返时间总是正确的,因为我们首先添加两个单位(接收+原始),然后减去两个单位(返回 - 发送).

如果我们假设单向时间总是往返时间的一半(即发送时间是接收时间,那么我们可以很容易地计算时间差,如下所示):

Oneway = Roundtrip / 2 = 6 / 2 = 3
Difference = Sending - Oneway = 5 - 3 = 2
Run Code Online (Sandbox Code Playgroud)

如您所见,我们准确地将时差计算为2个单位.时差的等式总是sending - oneway时间.但是,此等式的准确性取决于您计算单程时间的准确程度.如果发送和接收消息的实际时间不相等或大致相等,则需要找到其他方法来计算单向时间.但是,为了您的目的,这应该足够了.

  • 我认为会有2个问题,如果我错了就纠正我:1)ICMP时间戳使用ICMP作为传输协议.这个算法使用TCP,所以我认为TCP的3路握手会引入显着和不可预测的延迟(并且单向不是往返/ 2).即使是更糟糕的情况,如果POST涉及"CORS预检"以获得跨域ajax的授权.也许利用HTTP持久连接可以成为提高准确性的可行解决方案?(例如,打开一个虚拟请求来初始设置连接而不是使用建议的算法?) (2认同)
  • 2)服务器端的$ receive.shp不代表接收数据包的时间.它仅在服务器没有负载时才有效.在常见情况下,Web服务器将请求放入队列中,并且php进程池在FIFO逻辑中处理请求.在该实现中,不可能区分从php处理处理分组时接收分组的时间.如何识别响应是有效还是无效?(例如,由于引入延迟的请求突发?) (2认同)

Fre*_*der 5

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

服务器端点

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

http://localhost:3000/api/time
Run Code Online (Sandbox Code Playgroud)

返回:

status: 200, 
body: { time: {{currentTime}} }
Run Code Online (Sandbox Code Playgroud)

如何做到这一点取决于您使用的后端语言。

客户代码

鉴于这样的端点,我拼凑的这个 JS 片段将

  1. 轮询您的服务器端点 10 次。
  2. 尝试通过移除一半的往返时间来补偿延迟。
  3. 报告服务器和客户端之间的平均偏移量。

http://localhost:3000/api/time
Run Code Online (Sandbox Code Playgroud)

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

  • 这很有用,谢谢。使用中值而不是均值来拒绝异常值也可能是有意义的。 (2认同)