NTP(Network Time Procotol,网络时间协议)是今天仍在使用的最古老的互联网协议之一。它从 1985 年首次被实现以来,已经为互联网中的无数台设备提供了时间校准的服务,成为了大部分依赖于准确时间的应用得以实现的基础。本文中,我们将简要介绍 NTP 的一个参考实现,以在读者的心中建立 NTP 协议的原理框架。
NTP 是什么
NTP 是一种通过网络在计算机之间进行时钟同步的协议,它工作在 OSI 模型的应用层,通过一系列原理与算法,实现以极小的误差,将所有网络中的计算机与 UTC 同步。
由于时钟硬件精度的限制,离线的设备不总是能时刻与 UTC 同步,误差随着时间累积使计算机的本地时钟产生较大的偏差。此外,设备初次启动,启动前时钟仍处于默认状态,也需要与现在的时间同步。因此,通过互联网与可靠的时间源同步是必要的。通过这一协议,设备将寻找合适的同步源,将自身时钟与同步源同步,以保证依赖时间的应用能正常运行。
NTP 的网络结构
在 NTP 中,计算机之间的关系形如下图中的分层系统:
图 2-1:NTP 的网络结构
在这个系统中,层次被称为 stratum。如图所示,提供精确时间的时间源被认为在第 0 层(Stratum 0),它们与第 1 层的计算机(称为主要服务器)直接相连(表示为黄色箭头),而在下面的层中,既存在与上一层的同步,也存在层次内部的同步,它们都是通过网络连接进行的(表示为红色箭头)。
NTP 的层次最多可存在 15 层,第 16 层用于表示该设备未同步。通过一系列时间源的选择算法,可以确保上述结构形成了某种最优的生成树结构,使得每台计算机到第 1 层的路径都是最优路径。
NTP 中的时间与数据格式
NTP 的数据格式
图 3-1:NTP 的数据格式
如图所示,NTP 定义了两种数据格式:时间戳(timestamp)和日期戳(datestamp)。它们分别提供不同范围的时间表示法。
时间戳长 64 位,前 32 位表示从时代起点(era epoch,目前起点为 1900/1/1 00:00:00)开始的秒数,后 32 位表示秒数的小数部分。这样的时间表示具有 232 皮秒(即 232×10-12 秒)的精度。但这种表示法只能表示有限范围内的时间,因此时间戳存在一个 136 年的周期,一个周期称为一个 NTP 的时代(era)。下一个时代的起点在 2036 年。
日期戳长 128 位,前 32 位表示时代编号(从 0 开始),后 96 位是一个时间戳,分别为 32 位的秒数和 64 位的小数部分。容易发现,日期戳能表示的时间范围更广,精度也更高,它的跨度长于宇宙的年龄,精度小于光通过一个原子的时间。
时间戳常用于通信时的数据包中,而日期戳一般只在实现内部使用。
NTP 时间与 Unix 时间
NTP 标度与 Unix 标度只相差一个常数 2208988800,这是 1900/1/1 (NTP 标度起点)与 1970/1/1 (Unix 标度起点)之间的秒数差别。
NTP 的闰秒处理
广泛使用的时间是协调世界时间(UTC),但基于原子钟中原子的震荡计算的国际原子时(TAI)会因为地球自转的不规则变化与 UTC 产生差别。当差别过大时,我们通过+1s插入闰秒(leap second)的方式修正这种差别。
NTP 的时间基于 UTC,因此需要特殊处理插入闰秒的情形。为了满足时间的连续性和单调性,以及需要保证时间戳与现实时间的对应关系不改变,NTP 采用的方式是在闰秒时冻结时钟的增加。
图 3-2:闰秒处理的例子
如上图所示,闰秒插入在 23:59:59 之后,在进入闰秒时精确冻结时钟一秒,闰秒结束后再释放时钟。
从 1972 年以来插入的闰秒时间可以在文件 leap-seconds.list
(https://www.ietf.org/timezones/data/leap-seconds.list)中找到,通过这些信息可以计算 TAI 与 UTC 的差别,进而进行换算。
NTP 时间戳的运算
NTP 时间戳只支持作差运算,由于 NTP 时间戳的本质是定长小数,差运算可以看成是对 2 的整数次幂取模意义下的运算。因此,只要两个时间戳之间的间隔不超过 68 年(时间戳周期的一半),作差的结果就是正确的。
时间戳运算在协议中的应用会在之后的算法中具体介绍。