Лучший способ синхронизировать часы javascript на стороне клиента с датой сервера


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

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

7   51  

7 ответов:

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

эти две функции 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;
}

EDIT: удалил ".заменять.(^/() [\s\S]/,"$1")".

calcOffset () вычисляет смещение от времени сервера и компенсирует GMT/UTC.

getServerTime (), чтобы получить смещение по местному времени в соответствии с серверами, используя локальный часовой пояс.

Если calcOffset () занимает много времени, чтобы выполнить вы можете потерять несколько секунд точности. Может время выполнения может быть принято во внимание....

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

пример работает только в IE из-за " Msxml2.XMLHTTP " i думать.....

вы можете рассчитать точное время с NTP (протокол сетевого времени) в ваш код,

Я пытаюсь объяснить тебе:

  1. у нас есть ClientTime на отправку запроса (например 4/3/2012 13: 56: 10.123)
  2. вы отправляете ClientTime на сервер
  3. у нас есть времени для запроса, я назвал его RequestTime (например: это занимает 5 секунд)
  4. в сервере, мы вычисляем Разница во времени между сервером и клиентом (например: IT ServerTime - ClientTime = ServerClientDifferenceTimeWithRequesttime), теперь вы должны эту разницу, включая время запроса туда и обратно на Шаге 3, затем вы должны удалить время туда и обратно из Difference
  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);
});

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

Примечание

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

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

Это поможет, если мы лучше поймем, что вы на самом деле пытаетесь сделать.

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

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

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

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

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