Отказ от ответственности: Я ни в коем случае не эксперт по NTP. Просто любитель веселья на выходных.
Я понимаю, что вы сказали, что вам не нужна реализация NTP из-за предполагаемой сложности и из-за того, что сервер NTP в Интернете может быть недоступен в вашей среде.
Однако упрощенный поиск NTP может быть легко реализован, и если у вас есть локальный NTP-сервер, вы можете добиться хорошей синхронизации.
Вот как:
Отзыв RFC 5905
Вы увидите, что пакеты NTP v4 выглядят примерно так:
- LI (2 бита)
- VN (3 бита) - используйте «100» (4)
- Режим (3 бита)
- Stratum (8 бит)
- Опрос (8 бит)
- Точность (8 бит)
- Корневая задержка (32 бита)
- корневая дисперсия (32 бита)
- Идентификатор ссылки (32 бита)
- Контрольная временная метка (64 бита)
- Начальная временная метка (64 бита)
- Отметка времени приема (64 бита)
- Метка времени передачи (64 бита)
- Поле расширения 1 (переменная)
- Поле расширения 2 (переменная)
- ...
- Идентификатор ключа
- Дайджест (128 бит)
Дайджест не требуется, поэтому сформировать действительный запрос клиента очень просто. Следуя указаниям в RFC, используйте LI = '00', VN = '100' (десятичное число 4), Mode = '011' (десятичное число 3).
Использование C # для иллюстрации:
byte[] ntpData = new byte[48]
Array.Clear(ntpData, 0, ntpData.Length);
ntpData[0] = 0x23; // LI = 00, VN = 100, Mode = 011
Откройте сокет на целевом сервере и отправьте его.
int ntpPort = 123;
IPEndPoint target = new IPEndPoint(Dns.GetHostEntry(serverDnsName).AddressList[0], ntpPort);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
s.Connect(target);
s.Send(ntpData);
В ответе текущее время будет в метке времени передачи (байты [40 - 48]). Временные метки - это 64-разрядные числа без знака с фиксированной запятой. Целая часть - это первые 32 бита, дробная часть - последние 32 бита. Он представляет количество секунд с 0 ч 1 января 1900 года.
s.Receive(ntpData);
s.Close();
ulong intPart = 0;
ulong fractPart = 0;
for (int i = 0; i < 4; i++)
intPart = (intPart << 8) | ntpData[40 + i];
for (int i = 4; i < 8; i++)
fractPart = (fractPart << 8) | ntpData[40 + i];
Чтобы обновить часы с (примерно) второй степенью детализации, используйте: # секунд с 0 ч. Jan-1-1900 = intPart + (fractPart / 2 ^ 32). (Я говорю грубо, потому что сетевая задержка не учитывается, и мы здесь округляем)
ulong seconds = intPart + (fractPart / 4294967296);
TimeSpan ts = TimeSpan.FromTicks((long)seconds * TimeSpan.TicksPerSecond);
DateTime now = new DateTime(1900, 1, 1);
now = DateTime.SpecifyKind(now, DateTimeKind.Utc);
now += ts;
"сейчас" теперь DateTime с текущим временем в UTC.
Хотя это может и не ответить на ваш вопрос, надеюсь, это сделает NTP немного менее непрозрачным. =)