将客户端 javascript 时钟与服务器日期同步的最佳方式

IT技术 javascript ajax timezone clock-synchronization
2021-02-08 08:20:53

我有一项任务是在某个固定时区(MSK 或 MSD - 取决于当前日期)的 HTML 页面上显示数字时钟(分钟精度)。我想避免依赖客户端系统时钟,因此需要与服务器进行一些同步。HTTP 服务器在每个响应中发送 Date 标头,因此我们可以向我们站点的任何 URL 发送 AJAX GET 或 HEAD 请求以获取服务器日期,计算与客户端日期的差异并在使用 setTimeout() 更新时钟时使用它。还有其他问题仍然存在:日光设置的时区切换,连接非常慢的延迟。

对这个任务有最简单的想法吗?我更愿意在没有服务器端编程的情况下解决它。

6个回答

您可以在代码中使用NTP(网络时间协议)计算准确时间

我试着为你解释:

  1. 我们在发送请求时有 ClientTime(例如 4/3/2012 13:56:10.123)
  2. 您将 ClientTime 发送到服务器
  3. 我们有请求的往返时间,我称之为RequestTime(例如:需要5秒)
  4. 在服务器中,我们计算服务器和客户端之间的差异时间(例如:它 ServerTime - ClientTime = ServerClientDifferenceTimeWithRequestTime),您现在应该在第 3 步中将此差异包括往返请求时间,然后您应该从差异中删除往返时间
  5. 服务器发送响应,包括 ServerClientDifferenceTimeWithRequestTime 和 ServerTime
  6. 我们有响应的往返时间,我称之为响应时间(例如:需要 3 秒)
  7. 在客户端,我们再次计算 Server 和 Client 之间的时差(例如:It ServerTime - ClientTime = ServerClientDifferenceTimeWithResponseTime),再次:您现在应该在步骤 6 中计算包括往返响应时间在内的差值
  8. 我们现在有时间在客户端
  9. 您应该在客户端计算简单的方程:

X(同步时间) =Now + (ServerClientDifferenceTimeWithRequestTime - RquestTime)

X(同步时间) =Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

Now - ClientTime = RquestTime + ResponseTime =>

Now - (ServerClientDiffRq - RquestTime) = Now - (ServerClientDiffRs - ResponseTime)

如果你解决了它,你会发现这个:

ResponseTime = (ServerClientDifferenceTimeWithRequestTime - Now + ClientTime + - ServerClientDifferenceTimeWithResponseTime )/2

然后您可以使用以下等式在客户端中找到同步时间或服务器时间:

X(同步时间) =Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

我展示了简单的代码,但是当你想写它时不要忘记使用 UTC 日期和时间函数......

服务器端(例如 php、c#):

PHP:

header('Content-Type: application/json; charset=utf-8');
$clientTime = $_GET["ct"] * 1; //for php 5.2.1 or up: (float)$_GET["ct"];
$serverTimestamp = round(microtime(true)*1000); // (new DateTime())->getTimestamp();
$serverClientRequestDiffTime = $serverTimestamp - $clientTime;
echo "{\"diff\":$serverClientRequestDiffTime,\"serverTimestamp\":$serverTimestamp}";

C#:

long clientTime = long.Parse(Request.Form["ct"]);
long serverTimestamp = (DateTime.Now.Ticks-(new DateTime(1970,1,1) - DateTime.MinValue).Ticks) / 10000;
long serverClientRequestDiffTime = serverTimestamp - clientTime;
Response.Write("{\"diff\":"+serverClientRequestDiffTime+",\"serverTimestamp\":"+serverTimestamp+"}");

客户端(带有Jquery 的Javascript ):

var clientTimestamp = (new Date()).valueOf();
$.getJSON('http://yourhost.com/getdatetimejson/?ct='+clientTimestamp, function( data ) {
    var nowTimeStamp = (new Date()).valueOf();
    var serverClientRequestDiffTime = data.diff;
    var serverTimestamp = data.serverTimestamp;
    var serverClientResponseDiffTime = nowTimeStamp - serverTimestamp;
    var responseTime = (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp - serverClientResponseDiffTime )/2

    var syncedServerTime = new Date((new Date()).valueOf() + (serverClientResponseDiffTime - responseTime));
    alert(syncedServerTime);
});
因为客户端的公式是错误的,所以被否决了。正确的公式是 syncedServerTime = clientTime + (data.Diff + (data.ServerTime - nowTimeStamp)) / 2
2021-03-13 08:20:53
@Mehdi:它应该是 (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp + serverClientResponseDiffTime )/2 因为 serverClientResponseDiffTime 是计算服务器端
2021-03-18 08:20:53
en.wikipedia.org/wiki/Network_Time_Protocol它的 ntp 协议..我使用了时钟同步算法
2021-03-27 08:20:53
他使用相同的基本算法来计算 NTP 如何计算服务器和客户端时间之间的差异——非常聪明的东西。请参阅NTP 页面的这一部分
2021-03-28 08:20:53
我修复了您遇到的一些错误,但这实际上不起作用。如果客户端时钟关闭,这不会给出服务器看到的正确时间。
2021-04-05 08:20:53

这两个 Javascript 函数应该可以为您解决问题。

var offset = 0;
function calcOffset() {
    var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    xmlhttp.open("GET", "http://stackoverflow.com/", false);
    xmlhttp.send();

    var dateStr = xmlhttp.getResponseHeader('Date');
    var serverTimeMillisGMT = Date.parse(new Date(Date.parse(dateStr)).toUTCString());
    var localMillisUTC = Date.parse(new Date().toUTCString());

    offset = serverTimeMillisGMT -  localMillisUTC;
}

function getServerTime() {
    var date = new Date();

    date.setTime(date.getTime() + offset);

    return date;
}

编辑:删除了“.replace(/^(. )[\s\S] /,”$1”)”。

calcOffset() 计算与服务器时间的偏移量并补偿 GMT/UTC。

getServerTime() 使用本地时区获取本地时间偏移量以匹配服务器。

如果 calcOffset() 需要很长时间来执行,您可能会失去几秒钟的精度。也许可以考虑执行时间......

如果您担心本地时间或服务器时间更改为夏令时或从夏令时更改时计算的偏移量会出错,您可以在每个时钟小时后重新计算一点,系统将补偿夏令时的变化。可能需要等到本地时钟和服务器时钟都过去了一个小时。

该示例仅适用于 IE,因为我认为“Msxml2.XMLHTTP”.....

@RobG 或 Date.now()
2021-03-16 08:20:53
将方法从“GET”更改为“HEAD”将导致 http 服务器不返回正文。这可能会加快请求速度。
2021-03-19 08:20:53
这个奇怪的正则表达式是做什么的:.replace(/^(.*)[\s\S]*/,"$1")
2021-03-27 08:20:53
请注意,Date.parse(new Date().toUTCString());创建一个日期对象,将其转换为 UTC 字符串,然后将其解析回日期并返回自纪元以来的毫秒数。它可以被更简单的(new Date()).getTime();new Date() * 1
2021-03-28 08:20:53
我实际上是从一些旧来源复制了该部分。作者不记得是做什么的,我什至似乎没有有效的正则表达式......对不起......
2021-04-11 08:20:53

我发现上面@mehdi-yeganeh 的算法没有给我有用的结果,但这个想法是合理的:使用 NTP 算法(或至少是它的弱版本)来同步服务器和客户端时钟。

这是我的最终实现,它使用服务器响应标头(如果有的话)以提高准确性(如果我错了,请纠正我,我自己的测试表明这是非常准确的)。

浏览器端(javascript):

// the NTP algorithm
// t0 is the client's timestamp of the request packet transmission,
// t1 is the server's timestamp of the request packet reception,
// t2 is the server's timestamp of the response packet transmission and
// t3 is the client's timestamp of the response packet reception.
function ntp(t0, t1, t2, t3) {
    return {
        roundtripdelay: (t3 - t0) - (t2 - t1),
        offset: ((t1 - t0) + (t2 - t3)) / 2
    };
}

// calculate the difference in seconds between the client and server clocks, use
// the NTP algorithm, see: http://en.wikipedia.org/wiki/Network_Time_Protocol#Clock_synchronization_algorithm
var t0 = (new Date()).valueOf();

$.ajax({
    url: '/ntp',
    success: function(servertime, text, resp) {
        // NOTE: t2 isn't entirely accurate because we're assuming that the server spends 0ms on processing.
        // (t1 isn't accurate either, as there's bound to have been some processing before that, but we can't avoid that)
        var t1 = servertime,
            t2 = servertime,
            t3 = (new Date()).valueOf();

        // we can get a more accurate version of t2 if the server's response
        // contains a Date header, which it generally will.
        // EDIT: as @Ariel rightly notes, the HTTP Date header only has 
        // second resolution, thus using it will actually make the calculated
        // result worse. For higher accuracy, one would thus have to 
        // return an extra header with a higher-resolution time. This 
        // could be done with nginx for example:
        // http://nginx.org/en/docs/http/ngx_http_core_module.html
        // var date = resp.getResponseHeader("Date");
        // if (date) {
        //     t2 = (new Date(date)).valueOf();
        // }

        var c = ntp(t0, t1, t2, t3);

        // log the calculated value rtt and time driff so we can manually verify if they make sense
        console.log("NTP delay:", c.roundtripdelay, "NTP offset:", c.offset, "corrected: ", (new Date(t3 + c.offset)));
    }
});

服务器端(php,但可以是任何东西):

您在路由 'GET /ntp' 上的服务器应该返回如下内容:

echo (string) round(microtime(true) * 1000);

如果您的 PHP > 5.4,那么您可以保存对 microtime() 的调用并使其更准确:

echo (string) round($_SERVER['REQUEST_TIME_FLOAT'] * 1000);

笔记

这种方式可能被视为一种贫民窟,还有一些其他 Stack Overflow 答案可以指导您找到更好的解决方案:

如果我能给答案加星标,我会的。
2021-03-14 08:20:53
github.com/nicksardo/GoTime是另一个类似于 ServerDate 的 JS 库,但具有用于 Websockets 的处理程序,这大大提高了典型 http 请求的精度。
2021-03-24 08:20:53
t2 校正效果不佳,因为它只是秒分辨率,而不是像其他分辨率一样的毫秒。它实际上使准确性变得更糟。
2021-03-25 08:20:53
谢谢你的提醒。我什至没有想到时间分辨率问题。我会在我的回答中将其注释掉,并记下您的评论。
2021-04-04 08:20:53
+1。我实际上认为这应该是公认的答案 - 在我的实现中,来自服务器的每个响应都包含带有 t1 和 t2(具有 ms 精度)的自定义 HTTP 标头,其余的就像你写的一样。
2021-04-09 08:20:53

如果您打算使用 ajax,您应该记住 readyState==2 和 readyState==3 之间的客户端时间,因为服务器时间将设置在收到请求和准备响应之间的某个时间

我没有提供使用 jquery/mootools/prototype 的例子,因为它们都隐藏了浏览器原生 xmlhttprequest 对象的工作,并且没有提供处理 readystatechange 事件的接口
2021-03-15 08:20:53
任何使用 jquery 或其他一些 JS 框架隐藏 AJAX 实现差异的示例?
2021-03-31 08:20:53

如果您只需要精确到分钟,我只会每 30 秒左右从服务器请求更新一次。不要完全依赖客户端时间,而是使用他们的系统时钟来保持更新之间的时钟准确。我想你回答了你自己的问题?

如果我们更好地理解您实际尝试做什么,将会有所帮助。

如果您只是想要一个时钟在服务器上显示时间,然后将其调整到某个时区,请在客户端使用偏移量进行处理。通过使用您从服务器收到的日期,在适用的时区处理 DST。如果您想确定延迟,您可能需要服务器上的一个小脚本来计算差异。但如上所述,这将有助于更好地理解问题。如果精确到分钟,延迟似乎就不那么重要了。

单次同步就足以得到差异。问题是关于正确处理时区和记帐延迟,或者是解决问题的更好方法。
2021-03-21 08:20:53
我相信“显示数字时钟”意味着一些带有 javascript 更新它的 HTML 标记。
2021-03-21 08:20:53
那么问题就不是很清楚了,正如评论者认为提问者想要更改客户端时钟所双重指出的那样。
2021-04-02 08:20:53