web-dev-qa-db-ja.com

クライアント側のJavaScriptクロックをサーバーの日付と同期させる最良の方法

固定のタイムゾーン(現在の日付に応じてMSKまたはMSD)でHTMLページにデジタル時計(分単位の精度)を表示するタスクがあります。クライアントシステムのクロックに依存しないようにしたいので、サーバーとの同期が必要です。 HTTPサーバーは各応答でDateヘッダーを送信するため、AJAX GETまたはHEADサーバーの日付を取得するためにサイトの任意のURLにリクエストを送信し、差を計算できますクライアントの日付を使用し、setTimeout()でクロックを更新するときに使用します他の問題が残っています:夏時間設定のタイムゾーン切り替え、非常に遅い接続の遅延アカウンティング。

このタスクの最も簡単な方法はありますか?サーバー側のプログラミングなしで解決したいと思います。

56
n0s

ajaxを使用する場合は、readyState == 2とreadyState == 3の間のクライアント時間を覚えておく必要があります。サーバー時間は、リクエスト受信時と応答準備時の間のどこかに設定されるためです。

9

これらの2つの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()の実行に時間がかかる場合、数秒の精度が失われる可能性があります。たぶん、実行時間を考慮することができます。

現地時間またはサーバー時間が夏時間に変更されたり、夏時間から変更されたときに計算オフセットが間違っていることが心配な場合は、システムが夏時間の変更を補正します。ローカルクロックとサーバークロックの両方が1時間を経過するまで待つ必要がある場合があります。

この例は、IEでのみ機能します。これは、 "Msxml2.XMLHTTP"が原因だと思います.....

37
Fedearne

コードで NTP(Network Time Protocol) を使用して正確な時間を計算できます。

私はあなたのために説明しようとします:

  1. リクエストの送信時にClientTimeがあります(たとえば、4/3/2012 13:56:10.123)
  2. ClientTimeをサーバーに送信します
  3. リクエストにはラウンドトリップ時間があり、私はそれを呼び出しましたRequestTime(例:5秒かかります)
  4. サーバーでは、サーバーとクライアントの差の時間を計算します(例:It ServerTime-ClientTime = ServerClientDifferenceTimeWithRequestTime)、ステップ3で往復要求時間を含むこの差を今すぐにする必要があります。次に、差からラウンドトリップ時間を削除する必要があります
  5. ServerClientDifferenceTimeWithRequestTimeおよびServerTimeを含むサーバー送信応答
  6. 応答には往復時間があり、私はそれを呼び出しましたResponseTime(例:3秒かかります)
  7. クライアントでは、サーバーとクライアント間の差分時間を再度計算します(例: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+"}");
_

クライアント側(Javascript Jquery ):

_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);
});
_
33
Mehdi Yeganeh

上記の@ 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);

注意

この方法は一種のゲットーと見なされる場合がありますが、より良いソリューションに導くことができる他のスタックオーバーフローの答えがいくつかあります。

24
Aktau

@Mehdi Yeganehと@Fedearneに感謝します。両方のロジックを使用するように関数を実装し、それが機能しています。

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

0
EThaizone Jo

わずか1分までの精度が必要な場合は、30秒ごとにサーバーに更新を要求するだけです。クライアントの時間にまったく依存せず、システムクロックを使用して、更新と更新の間のクロックを正確に保ちます。あなたはあなた自身の質問に答えたと思いますか?

あなたが実際に何をしようとしているのかをよりよく理解できれば助かります。

特定のタイムゾーンに合わせて調整されるサーバー上の時刻を表示するだけの時計が必要な場合は、クライアント側でオフセットを使用してください。サーバーから受信した日付も使用して、適用可能なタイムゾーンでDSTを処理します。待機時間を判断する場合は、おそらくサーバー上に小さなスクリプトが必要で、差を計算します。しかし、上記のように、問題をよりよく理解するのに役立ちます。精度がほんのわずかな場合、待ち時間はそれほど重要ではないようです。

0
snicker