一.NTP基础
1.NTP定义
NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用于分布式设备(比如电脑、手机、智能手表等)进行时间同步,避免人工校时的繁琐和由此引入的误差,方便快捷地实现多设备时间同步。
NTP校时服务基于UDP传输协议进行报文传输,工作端口默认为123/udp。
NTP使用协调世界时(UTC)以极高的精度同步计算机时钟时间,例如在局域网(LAN)中低至1毫秒,在互联网上则在数十毫秒内。
2.NTP 网络结构
服务器按层级关系连接,每一级称为一个层数(stratum),如主时间服务器层数为 stratum 1,二级时间服务器层数为 stratum 2,以此类推。时钟层数越大,准确性越低。
在局域网(LAN)中低至1毫秒,在互联网上则在数十毫秒内。
3.NTP 报文格式
NTP有两种不同类型的报文,一种是时钟同步报文,另一种是控制报文。控制报文仅用于需要网络管理的场合,它对于时钟同步功能来说并不是必需的,这里不做介绍。
时钟同步报文封装在UDP报文中,其格式如图所示:
各主要字段解释如下:
LI (Leap Indicator) : 长度为2比特,值为“11”时表示告警状态,时钟未被同步。为其他值时NTP本身不做处理。
VN (Version Number): 长度为3比特,表示NTP的版本号.
Mode:长度为3比特,表示NTP的工作模式。不同的值所表示的含义分别是:0未定义、1表示主动对等体模式、2表示被动对等体模式、3表示客户模式、4表示服务器模式、5表示广播模式或组播模式、6表示此报文为NTP控制报文、7预留给内部使用。
Stratum:系统时钟的层数,取值范围为1~16,它定义了时钟的准确度。层数为1的时钟准确度最高,准确度从1到16依次递减,层数为16的时钟处于未同步状态,不能作为参考时钟。
Poll:轮询时间,即两个连续NTP报文之间的时间间隔。
Precision:系统时钟的精度。
Root Delay:本地到主参考时钟源的往返时间。
Root Dispersion:系统时钟相对于主参考时钟的最大误差。
Reference Identifier:参考时钟源的标识。
Reference Timestamp:系统时钟最后一次被设定或更新的时间。
Originate Timestamp:NTP请求报文离开发送端时发送端的本地时间。
Receive Timestamp:NTP请求报文到达接收端时接收端的本地时间。
Transmit Timestamp:应答报文离开应答者时应答者的本地时间。
Authenticator:验证信息。
其中,NTP发送和接收的报文数据包类似,通常只需要前48个字节就能进行授时和校时服务。下面分别是抓包获取的NTP请求数据包和回复数据包示例(仅前48个字节):
请求数据包:
1B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ------------头部
D0 AF 5F F5 23 D7 08 00---------------Originate Timestamp ()出发时间戳有效位为8 bytes
回复数据包:
1C 02 00 EC 00 00 06 EA 00 00 0C A2 C0 A8 33 CA----数据包头部,共16 bytes
D0 AF 5E A3 F5 BD 72 BC---------------Reference Timestamp
D0 AF 5F F5 23 D7 08 00---------------Originate Timestamp ()
D0 AF 61 D7 CD 2E F9 11---------------Receive Timestamp ()
D0 AF 61 D7 CD 2F F4 BA---------------Transmit Timestamp ()
收到数据包后,接收端本地再产生一个时间戳()。
这里,每个返回数据前4字节为秒的整数部分,后4字节为秒的小数部分。
4.NTP时钟服务器
下面补充一些常用的NTP时钟服务器:
名称 地址
北斗授时服务 114.255.121.193
清华TUNA协会 ntp.tuna.tsinghua.edu.cn
国家NTP授时 ntp.ntsc.ac.cn
中国NTP快速授时 cn.ntp.org.cn
教育网NTP授时 edu.ntp.org.cn
更多NTP授时服务器请查看:
http://www.ntp.org.cn/pool
https://dns.iui.im/ntp/
pool.ntp.org: the internet cluster of ntp servers
二.QT代码示例
- UDP请求包
void NtpClient::sendData()
{
qint8 LI = 0;
qint8 VN = 3;
qint8 MODE = 3;
qint8 STRATUM = 0;
qint8 POLL = 4;
qint8 PREC = -6;
QDateTime epoch(QDate(1900, 1, 1), QTime(0, 0, 0));
qint32 second = quint32(epoch.secsTo(QDateTime::currentDateTime()));
qint32 temp = 0;
QByteArray timeRequest(48, 0);
timeRequest[0] = (LI << 6) | (VN << 3) | (MODE);
timeRequest[1] = STRATUM;
timeRequest[2] = POLL;
timeRequest[3] = PREC & 0xff;
timeRequest[5] = 1;
timeRequest[9] = 1;
timeRequest[40] = (temp = (second & 0xff000000) >> 24);
temp = 0;
timeRequest[41] = (temp = (second & 0x00ff0000) >> 16);
temp = 0;
timeRequest[42] = (temp = (second & 0x0000ff00) >> 8);
temp = 0;
timeRequest[43] = ((second & 0x000000ff));
udpSocket->write(timeRequest);
}
- UDP接收报文及解析
void NtpClient::readData()
{
QByteArray newTime;
QDateTime epoch(QDate(1900, 1, 1), QTime(0, 0, 0));
QDateTime unixStart(QDate(1970, 1, 1), QTime(0, 0, 0));
while (udpSocket->hasPendingDatagrams()) {
newTime.resize(udpSocket->pendingDatagramSize());
udpSocket->read(newTime.data(), newTime.size());
};
QByteArray transmitTimeStamp ;
transmitTimeStamp = newTime.right(8);
quint32 seconds = transmitTimeStamp.at(0);
quint8 temp = 0;
for (int i = 1; i <= 3; ++i) {
seconds = (seconds << 8);
temp = transmitTimeStamp.at(i);
seconds = seconds + temp;
}
QDateTime dateTime;
uint secs = seconds - epoch.secsTo(unixStart);
dateTime.setTime_t(secs);
udpSocket->disconnectFromHost();
if (dateTime.isValid()) {
Q_EMIT receiveTime(dateTime);
}
}