Table of Contents

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 gitlab repozitáři.

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:

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:

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.