Лучший способ синхронизировать клиентские часы javascript с серверной датой - PullRequest
54 голосов
/ 28 октября 2009

У меня есть задача показать цифровые часы (с точностью до минут) на странице HTML в некотором фиксированном часовом поясе (MSK или MSD - в зависимости от текущей даты). Я бы не хотел полагаться на системные часы клиента, поэтому требуется некоторая синхронизация с сервером. HTTP-сервер отправляет заголовок Date в каждом ответе, поэтому мы можем отправить запрос AJAX GET или HEAD на любой URL нашего сайта, чтобы получить дату сервера, рассчитать разницу с датой клиента и использовать ее при обновлении часов с помощью setTimeout (). Остаются другие проблемы: переключение часового пояса для настроек дневного света, задержка с учетом очень медленных соединений.

Есть идеи для этой задачи самым простым способом? Я предпочел бы решить это без программирования на стороне сервера.

Ответы [ 7 ]

38 голосов
/ 29 октября 2009

Эти две функции 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", я думаю .....

32 голосов
/ 03 апреля 2013

Вы можете рассчитать точное время с помощью NTP (Network Time Protocol) в ваших кодах,

Я пытаюсь объяснить для вас:

  1. У нас есть ClientTime при отправке запроса (например, 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);
});
23 голосов
/ 09 апреля 2014

Я обнаружил, что алгоритм @ 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);

Примечание

Этот способ может рассматриваться как разновидность гетто, есть и другие ответы о переполнении стека, которые могут помочь вам найти лучшее решение:

9 голосов
/ 28 октября 2009

вы должны помнить время клиента между readyState == 2 и readyState == 3, если вы собираетесь использовать ajax, потому что время сервера будет установлено где-то между временем по получении запроса и подготовленным ответом

0 голосов
/ 08 марта 2016

Спасибо @Mehdi Yeganeh и @Fedearne. Я реализую свою функцию, чтобы использовать как логику, так и ее работу.

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

0 голосов
/ 28 октября 2009

Я бы синхронизировал время при инициализации с Интернет-сервером времени.

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

0 голосов
/ 28 октября 2009

Я буду запрашивать обновление с сервера каждые 30 секунд или около того, если вам требуется точность только до минуты. Вообще не полагайтесь на время клиента, а используйте его системные часы для обеспечения точности часов между обновлениями. Я думаю, что вы ответили на свой вопрос?

Было бы полезно, если бы мы лучше поняли, что вы на самом деле пытаетесь сделать.

Если вы просто хотите, чтобы часы отображали время на сервере, которое затем настраивается на определенный часовой пояс, сделайте это на стороне клиента со смещениями. Обрабатывать летнее время в часовых поясах, к которым оно применимо, используя дату, которую вы также получаете от сервера. Если вы хотите определить задержку, вам, вероятно, понадобится небольшой скрипт на сервере, чтобы рассчитать разницу. Но, как указано выше, это поможет лучше понять проблему. Если точность составляет всего минуту, задержка кажется менее критичной.

...