簡體   English   中英

將客戶端 javascript 時鍾與服務器日期同步的最佳方式

[英]The best way to synchronize client-side javascript clock with server date

我有一項任務是在某個固定時區(MSK 或 MSD - 取決於當前日期)的 HTML 頁面上顯示數字時鍾(分鍾精度)。 我想避免依賴客戶端系統時鍾,因此需要與服務器進行一些同步。 HTTP 服務器在每個響應中發送 Date 標頭,因此我們可以向我們站點的任何 URL 發送 AJAX GET 或 HEAD 請求以獲取服務器日期,計算與客戶端日期的差異並在使用 setTimeout() 更新時鍾時使用它。 還有其他問題仍然存在:日光設置的時區切換,連接速度非常慢的延遲。

對這個任務有最簡單的想法嗎? 我更願意在沒有服務器端編程的情況下解決它。

您可以在代碼中使用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(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);
});

這兩個 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”.....

我發現上面@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 答案可以指導您找到更好的解決方案:

如果您打算使用 ajax,您應該記住 readyState==2 和 readyState==3 之間的客戶端時間,因為服務器時間將設置在收到請求和准備響應之間的某個時間

如果您只需要精確到分鍾,我只會每 30 秒左右從服務器請求更新一次。 不要完全依賴客戶端時間,而是使用他們的系統時鍾來保持更新之間的時鍾准確。 我想你回答了你自己的問題?

如果我們更好地理解您實際嘗試做什么,將會有所幫助。

如果您只是想要一個時鍾在服務器上顯示時間,然后將其調整到某個時區,請在客戶端使用偏移量。 通過使用您從服務器收到的日期,在適用的時區處理 DST。 如果您想確定延遲,您可能需要服務器上的一個小腳本來計算差異。 但如上所述,這將有助於更好地理解問題。 如果精確到分鍾,延遲似乎就不那么重要了。

感謝@Mehdi Yeganeh 和@Fedearne。 我實現了我的功能以使用這兩種邏輯並且它的工作原理。

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

太晚了,但希望這可以幫助某人!

無論客戶端的機器如何,我都有類似的要求來顯示服務器時鍾。 所以基本上,你只需在這里使用三個參數:

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>

如果我正確理解問題,我們只有 3 個值

  1. 客戶端發送請求時間
  2. 服務器時間
  3. 客戶端接收響應時間

如果假設請求和響應時間相等,我們可以通過以下方式計算服務器和客戶端之間的 timeDifference:

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

並在客戶的幫助下獲得正確的時間

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

我會在初始化期間與 Internet 時間服務器同步時間。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM