Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

6. NTP klient

Jednou ze síťových služeb, se kterými lze komunikovat na velmi nízké úrovni (byty) a přesto získat užitečnou informaci, jsou NTP servery. NTP slouží pro synchronizaci hodin počítačů a využívá UDP komunikační protokol na portu 123. Detaily o NTP protokolu a implementaci klienta v C lze najít zde.

Kompletní kód je ke stažení v git repozitáři nebo ve formě balíčku: ppc-sol06.zip

Vytvoření UDP klienta

Vytvoření UDP klienta je jednoduché a spočívá ve vytvoření instance třídy QUdpSocket a připojení k hostiteli. Pro práci s adresou hostitele využijeme třídu QHostInfo a připojíme se metodou QAbstractSocket::connectToHost() k některému z časových serverů, které provozuje sdružení CESNET:

  • tik.cesnet.cz, 195.113.144.201, řízen atomovými hodinami
  • tak.cesnet.cz, 195.113.144.238, řízen GPS

QHostInfo host = QHostInfo::fromName("tik.cesnet.cz");
QUdpSocket *s = new QUdpSocket;
s->connectToHost(QHostAddress(host.addresses().at(0)), 123);

Odeslání a přijetí datagramu

NTP server očekává požadavek ve formě zprávy o délce právě 48 bytů a právě tak dlouho zprávou klientovi i odpoví. Pro požadavek je klíčový první byte, jehož hodnota je 0x0B1) Existuje pochopitelně několik cest, jak takový paket vytvořit, zde využijeme toho, že na hodnotě ostatních bytů nezáleží a nastavíme všechny na stejnou hodnotu:

s->write(QByteArray(48, 0x0B));

Po přijmutí požadavku server odpoví packetem stejné velikosti. Aby klient neukončil svoji činnost dříve, než packet přijde, může využít např. blokující funkci QAbstractSocket::waitForReadyRead(), která čeká na přijetí dat. Argumentem metody je maximální doba čekání (timeout), který je defaultně nastaven na 30000 ms. Po přijetí datagramu lze datagram číst po částech metodou QIODevice::read() nebo celou najednou metodou QIODevice::readAll(). Zda jsou data ke čtení lze zjistit metodou QUdpSocket::hasPendingDatagrams().

if (s->waitForReadyRead()){
    while (s->hasPendingDatagrams()) {
        QByteArray data = s->readAll();
    }
}

Dekódování datagramu

V poli 48 bytů, které vrací server klientovi, je řada užitečných informací. Pro naše účely je zajímavá předposlední čtveřice bytů, číslo typu unsigned int, které reprezentuje počet vteřin od 1.1.1900 00:00:00. Při dekódování zprávy je třeba vyřešit dva problémy:

  • data ze serveru jsou ve formátu big endian (network byte order), ale formát dat klienta bude velmi pravděpodobně little endian. Pokud jsme si jisti endianitou klienta, lze sestavit informaci o čase ve správném pořadí. Případně lze využít funkce ntohl(), která je v OS Windows součástí knihovny WinSock
  • NTP server vrací počet vteřin od 1.1.1900 00:00:00. Formáty reprezentace času, které se běžně používají v operačních systémech, počítají čas od 1.1.1970 00:00:00 (Unix epoch). Pro získání správného času je proto třeba odečíst od čísla, které vrací NTP server rozdíl mezi těmito dvěma časy, což je právě 2208988800 vteřin.

unsigned long cas = uchar(data[43]) + (uchar(data[42]) << 8) + (uchar(data[41]) << 16) + (uchar(data[40]) << 24);
cas -= 2208988800U;
QDateTime *time = new QDateTime::fromSecsSinceEpoch(cas);
qDebug() << time->toString("yyyy-MM-dd HH:mm:ss");

1)
Podrobnosti lze nalézt ve specifikaci protokolu.
courses/b2b99ppc/solutions/06.txt · Last modified: 2020/05/15 22:30 by viteks