【问题标题】:The best way to synchronize client-side javascript clock with server date将客户端 javascript 时钟与服务器日期同步的最佳方法
【发布时间】:2010-12-10 22:25:27
【问题描述】:

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

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

【问题讨论】:

    标签: javascript ajax timezone clock-synchronization


    【解决方案1】:

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

    如果我们能更好地了解您的实际操作,将会有所帮助。

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

    【讨论】:

    • 单次同步就足够了。问题是关于正确处理时区和记帐延迟,或解决问题的更好方法。
    • 那么问题不是很清楚,正如评论者认为提问者想要更改客户时钟的双重暗示。
    • 我相信“显示数字时钟”是指一些带有 javascript 更新的 HTML 标记。
    【解决方案2】:

    我会在初始化期间与 Internet 时间服务器同步时间。

    http://tf.nist.gov/service/its.htm

    【讨论】:

    • 如果我不得不猜测,我会说它被否决了,因为答案没有提供从 javascript 使用 ITS 的方法
    【解决方案3】:

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

    【讨论】:

    • 有 jquery 或其他 JS 框架隐藏 AJAX 实现差异的示例吗?
    • 我没有提供使用 jquery/mootools/prototype 的示例,因为它们都隐藏了使用浏览器的本机 xmlhttprequest 对象的工作,并且不提供处理 readystatechange 事件的接口
    【解决方案4】:

    这两个 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”.....

    【讨论】:

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

    您可以在代码中使用NTP (Network Time Protocol) 计算准确时间,

    我试着为你解释一下:

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

    X(SyncedTime) = Now + (ServerClientDifferenceTimeWithRequestTime - RquestTime)

    X(SyncedTime) = Now + (ServerClientDifferenceTimeWithResponseTime - ResponseTime)

    Now - ClientTime = RquestTime + ResponseTime =>

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

    如果你解决它,你会发现:

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

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

    X(SyncedTime) = 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);
    });
    

    【讨论】:

    • en.wikipedia.org/wiki/Network_Time_Protocol它的ntp协议..我使用了时钟同步算法
    • 他使用相同的基本算法来计算 NTP 如何计算服务器和客户端时间之间的差异——非常聪明的东西。请参阅 NTP 页面的this part
    • 我修复了您遇到的一些错误,但这实际上不起作用。如果客户端时钟关闭,这不会给出服务器看到的正确时间。
    • 由于客户端的公式错误而被否决。正确的公式是syncedServerTime = clientTime + (data.Diff + (data.ServerTime - nowTimeStamp)) / 2
    • @Mehdi: 应该是 (serverClientRequestDiffTime - nowTimeStamp + clientTimestamp + serverClientResponseDiffTime )/2 因为 serverClientResponseDiffTime 是在服务器端计算的
    【解决方案6】:

    我发现上面@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 答案可以指导您找到更好的解决方案:

    【讨论】:

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

    感谢@Mehdi Yeganeh 和@Fedearne。我实现了我的函数以同时使用逻辑和它的工作。

    https://gist.github.com/ethaizone/6abb1d437dbe406fbed6

    【讨论】:

    • 仅链接的答案不好,因为它们可能会死。如果您想发布链接,可以在问题下方发表评论。
    【解决方案8】:

    有点太晚了,但希望这可能对某人有所帮助!

    我有类似的要求来显示服务器时钟,而与客户端的机器无关。 所以基本上,你只需要在这里使用三个参数:

    x = clientReqTimestamp = (new Date()).valueOf();  //Client Timestamp at request.
    y = serverTimestamp;  //query your server for Unix Timestamp.
    z = clientRespTimestamp = (new Date()).valueOf();  //Client Timestamp on receiving response.
    

    然后做如下计算:

    var reqElapsed = Math.abs(y - x);  //time taken in milliseconds to hit the server
    var respElapsed = Math.abs(z - y);  //time taken in milliseconds to get response from server
    var serverNewTime = z + respElapsed;  // Voila! actual server time.
    

    下面是完整的代码:

    <script>
           
        var clientTimestamp = (new Date()).valueOf();
        
        var Data = {
            OperatorMobileNo: 'XXXXXXXXXX',
            requestClientTime: clientTimestamp
        };
        $.ajax({
            type: "POST",
            url: serviceURLx + "/XXXX/GetServerDateTime/1.0",
            dataType: "JSON",
            data: JSON.stringify(Data),
            contentType: "application/json; charset=utf-8",
            success: function (responseData) {
                debugger;
                var responseJSON = JSON.parse(JSON.stringify(responseData));
                if (responseJSON.ResponseCode === "000") {
        
                    var x = clientReqTimestamp = clientTimestamp;
                        // If server time is in seconds => multiply by 1000 to convert sec to milli
                    var y = serverTimestamp = responseJSON.Response.ServTimestamp * 1000;
                    var z = clientRespTimestamp = (new Date()).valueOf();
                    
                    var reqElapsed = Math.abs(y - x);
                    var respElapsed = Math.abs(z - y);
    
                    var serverNewTime = z + respElapsed;
                    
                    debugger;
                    //Init Server Clock
                    setInterval( function() {
                        debugger;
                        var servClockT = new Date(serverNewTime += 1000);
                        document.getElementById('serverClock').innerHTML = servClockT;
                    }, 1000);
                }
                else {
                    swal("", "Unable To Fetch Server Time!", "info");
                    console.log(responseJSON.ResponseCode);
                }
            },
            error: function () {
            }
        });
    </script>
    

    【讨论】:

      【解决方案9】:

      如果我正确理解问题,我们只有 3 个值

      1. clientTimeWhenRequestSent
      2. 服务器时间
      3. clientTimeWhenResponseReceived

      如果假设请求和响应时间相等,我们可以计算 服务端和客户端的时间差:

      const diff = serverTime - clientTimeWhenRequestSent 
      - (clientTimeWhenResponseReceived - clientTimeWhenRequestSent)/2;
      

      并在客户的帮助下获得正确的时间

      const correctClienTime =  (new Date()).valueOf() + diff;
      

      【讨论】:

        猜你喜欢
        • 2011-05-15
        • 1970-01-01
        • 2011-01-24
        • 2018-10-18
        • 1970-01-01
        • 2013-04-19
        • 1970-01-01
        • 2014-09-30
        • 2013-11-16
        相关资源
        最近更新 更多