【发布时间】:2011-12-12 17:20:02
【问题描述】:
我正在寻找一种方法来以良好的精度(假设至少 0.5 秒)在客户端之间同步时间。
由于精度差(一秒或更短),我排除在服务器响应标头中使用 jsontime 或利用时间戳。
更新: 它甚至可以使用移动连接。 3G 连接本身的往返时间约为 0.5 秒并不少见(例如在意大利),因此算法必须是稳健的。
【问题讨论】:
标签: javascript ajax time ntp
我正在寻找一种方法来以良好的精度(假设至少 0.5 秒)在客户端之间同步时间。
由于精度差(一秒或更短),我排除在服务器响应标头中使用 jsontime 或利用时间戳。
更新: 它甚至可以使用移动连接。 3G 连接本身的往返时间约为 0.5 秒并不少见(例如在意大利),因此算法必须是稳健的。
【问题讨论】:
标签: javascript ajax time ntp
求助于古老的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
使用这些数据,我们可以计算:
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 时间。但是,此等式的准确性取决于您计算单程时间的准确性。如果发送和接收消息的实际时间不相等或近似相等,则需要找到其他方法来计算单向时间。但是,对于您的目的,这应该足够了。
【讨论】:
microtime() 不带参数返回“0.uuuuuu ssssssssss”形式的字符串。我宁愿echo round(microtime(true)*1000)。
request.open("POST", ...)。它也应该request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");,否则 PHP 可能会感到困惑。哦,顺便说一句,PHP 代码不需要提供两个时间戳(接收和传输),因为它们的差异不太可能超过一毫秒。也不需要原始时间戳来进行往返:JavaScript 可以记住它。因此,PHP 代码只需echo round(microtime(true) * 1000);,不需要更多。
如果你只是想在多台电脑上同步时钟,NTP 自己推荐setting up your own timeserver。
在您的服务器上设置一个 api 端点 即
http://localhost:3000/api/time
返回:
status: 200,
body: { time: {{currentTime}} }
这将取决于您使用的后端语言。
给定这样一个端点,我扔的这个 JS sn-p 会
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();
您可以使用这个计算出的偏移量(只需将其添加到本地时间),从每个客户端的上下文中确定一个通用的“通用”时间。
【讨论】: