おやまのエンジニアリングブログ

某ゲーム開発会社のフルスタックエンジニアしてます

サーバーとクライアントの時刻をシンクロさせる方法

本来ならNTP使ってろって話なんでしょうが、クライアントがNTP使ってる保証無いですし、絶対に合わせる必要があったので今回実装せざるを得なかったので実装しました。

まずサーバー側の時刻を正とした場合、一番単純な実装方法として、サーバーの時刻をクライアントに送信して、サーバーの時刻からクライアントの時刻を引いた数をクライアントの時刻に足すという実装が有ります。Javascript 辺りで書くと以下の様な感じです。

$.get("/echo/json", function(data, status, jqXHR) {
    var clientTime = new Date();
    var serverTime = new Date(Date.parse(jqXHR.getResponseHeader("Date")));
    var diff = clientTime - serverTime;
    var accurateTime = serverTime + diff;
    console.log(new Date(accurateTime));
});

ただ↑の実装だと、サーバーが時刻を生成してからクライアントに届くまでの時間が計算されていないので、クライアントの時刻が数ミリ秒~数秒遅れます。

これが結構難しい話で、そもそもお互いの時間がズレているので、サーバーからクライアントに届くまでに掛かった時間を計算することができません。

ただ、クライアントが通信を開始してから通信が完了する時間と、サーバーが通信を受け取ってから、応答を返すまでの時間は図ることができます。その2つを組み合わせることで通信全体に掛かった時間は割り出せます。それを2で割ると、サーバーとクライアント間の通信に掛かった時間がとれます。実装すると以下の様なコードになります。

var clientStart = new Date();
$.get("/echo/json", function(data, status, res) {
    var clientEnd = new Date();
    // ↓ サーバーの処理開始時刻を何かしらの方法で取得
    var serverStart = new Date(Date.parse(data.startTime));
    var serverEnd = new Date(Date.parse(res.getResponseHeader("Date")));
    var diff = clientEnd - serverEnd;
    var delay = ((clientEnd - clientStart) - (serverEnd - serverStart)) / 2;
    var accurateTime = new Date(new Date().getTime() + diff - delay);
    console.log(clientEnd);
    console.log(serverEnd);
    console.log(accurateTime);
});

実はこの実装も完璧ではありません。↑の計算は、送信した時に掛かった時間と応答した時に掛かった時間が同じであることを前提にした計算だからです。

更に正確に時刻を合わせる場合、NTPの実装のように、複数台のサーバーから時刻を取得して↑の計算を帝王するみたいにするともう少し正確な時刻が取れるようです。(自分はそこまでは実装しませんでしたが・・・)

全ての計算をラッピングしたクラスを実装するといい感じに抽象化できるのではないでしょうか。 自分はこれをC#(Unity)で実装したので、そのソースコードは後ほどGithubに載せる予定です。

リソース
Network Time Protocol - Wikipedia, the free encyclopedia

UPDATE: Gistのコード追加 (コンバーター付き)

Gist - taka-oyama / ServerTime.cs