NTP(Network Time Protocol)
NTP(Network Time Protocol):是一种用于同步计算机系统时间的协议。
NTP 通过分层的时间服务器架构,确保网络中所有设备的时间保持一致,精度可达毫秒甚至微秒级别,是互联网上最广泛使用的时间同步协议。

NTP就是上述过程的网络版:
设备(客户端) → 通过网络 → 问NTP服务器 → "现在几点?"
NTP服务器 → 回复一个精确时间戳 → 设备据此调整时钟
NTP报文格式(48B):
| 字节偏移 | 字段名称 | 大小 | 说明 |
|---|---|---|---|
| 0 | LI + VN + Mode | 1字节 | 闰秒+版本+模式 |
| 1 | Stratum | 1字节 | 服务器层级 |
| 2 | Poll | 1字节 | 轮询间隔 |
| 3 | Precision | 1字节 | 精度 |
| 4-7 | Root Delay | 4字节 | 根延迟 |
| 8-11 | Root Dispersion | 4字节 | 根离散度 |
| 12-15 | Reference ID | 4字节 | 参考源标识 |
| 16-23 | Reference Timestamp | 8字节 | 上次同步时间 |
| 24-31 | Origin Timestamp | 8字节 | 客户端发送时间(原样返回) |
| 32-39 | Receive Timestamp | 8字节 | 服务器收到时间 |
| 40-47 | Transmit Timestamp | 8字节 | 服务器发送时间 |
各字段具体含义:
| 字段名称 | 长度 | 含义说明 |
|---|---|---|
| LI (Leap Indicator) | 2 bits | 闰秒指示器 。警告客户端关于即将到来的闰秒(由于地球自转不均匀,UTC偶尔需要加1秒或减1秒)。 00: 无警告 01: 最后一分钟是59秒 10: 最后一分钟是61秒 11: 时钟未同步(告警状态) |
| VN (Version Number) | 3 bits | NTP 版本号 。目前常用的是版本 4(二进制 100)。 |
| Mode | 3 bits | 工作模式 。标识这个包是客户端发的还是服务端发的。3: 客户端 4: 服务器 6: 广播/组播等等 |
| Stratum | 8 bits | 层级 。表示时钟距离标准参考时钟(如原子钟、GPS)有多远。0: 无效或 unspecified 1: 直接连接参考时钟(如带GPS的NTP服务器) 2-15: 逐级向下同步的层级 16: 表示时钟未同步 |
| Poll | 8 bits | 轮询间隔 。表示连续两次NTP请求之间的最大时间间隔。这是一个以2为底的指数(如6表示 64秒)。 |
| Precision | 8 bits | 精度 。表示本地时钟的精度,也是以2为底的指数(通常为负数,如-10表示约1毫秒精度)。 |
| Root Delay | 32 bits | 根延迟 。从当前服务器到主参考时钟(Stratum 1)之间的总往返延迟时间(毫秒级)。 |
| Root Dispersion | 32 bits | 根离散度 。表示相对于主参考时钟的最大误差估计值。 |
| Reference ID | 32 bits | 参考标识符 。标识当前服务器是从哪里同步时间的。Stratum 1时通常是GPS、ATOM等ASCII码; Stratum 2及以上时通常是上游服务器的IP地址。 |
| Reference Timestamp | 64 bits | 参考时间戳 。本地时钟最后一次被设置或校正的时间(NTP时间戳格式)。 |
| Origin Timestamp | 64 bits | 源时间戳 。客户端发送请求时的本地时间(NTP时间戳格式)。 |
| Receive Timestamp | 64 bits | 接收时间戳 。服务器收到客户端请求时的本地时间(NTP时间戳格式)。 |
| Transmit Timestamp | 64 bits | 发送时间戳 。服务器把响应包发给客户端时的本地时间(NTP时间戳格式)。 |
NTP 是如何利用这些时间戳计算网络延迟的?
NTP之所以需要这4个时间戳,是为了消除网络传输带来的误差。计算公式如下(T1到T4均为时间点):
T1 (Origin) = 客户端发包时间
T2 (Receive) = 服务器收包时间
T3 (Transmit) = 服务器回包时间
T4 ( 到达客户端时间) = 客户端收到回复时的本地时间(这个不在包里,是客户端自己记录的)
通过这4个时间点,客户端可以算出:
- 网络往返延迟 = (T4 - T1) - (T3 - T2)
- 客户端与服务器的时间差 = ((T2 - T1) + (T3 - T4)) / 2
算出时间差后,客户端就会将这个差值(可能带有小数,即NTP时间戳的后32位)转换为操作系统支持的Unix时间戳格式,并调整系统时钟。
当NTP客户端和服务器(网络层)通过网络通信时,它们在数据包中交换的只有NTP时间戳 。但在操作系统层面需要转换 :当操作系统(如Linux/Windows)接收到NTP服务器的回复后,操作系统的内核或NTP守护进程(如ntpd/chronyd)会将收到的"NTP时间戳"在内存中转换为"Unix时间戳",然后再设置到系统时钟中:
NTP时间戳
为什么有NTP时间戳和其他时间戳(以Unix为例)?
它们诞生于不同的历史背景,服务于不同的层级(网络层 vs 操作系统层):
1. NTP时间戳(诞生于1985年之前):
结构 :64位长整型。前32位表示秒数 ,后32位表示小数秒(即纳秒/皮秒级精度),从1900年1月1日 00:00:00 UTC开始。
原因:NTP被设计用于跨网络同步高精度时间,需要极高的分辨率(小数秒)。选择1900年是因为当时设计NTP时,32位无符号整数能表示约136年,刚好覆盖到2036年(即NTP的"2036年溢出问题"),在当时看来是足够安全的。
2. Unix 时间戳(诞生于1970年代初):
结构 :通常是一个32位或64位整数,表示自1970年1月1日 00:00:00 UTC以来的整秒数(早期Unix系统不需要亚秒级精度)。
原因:Unix系统在设计时,为了在有限的硬件资源下方便计算日期,选择了1970年作为起点,且只记录整秒。著名的"Y2038问题"就是指32位Unix时间戳在2038年会溢出(靠物理升级到 64 位解决)。
NTP时间戳是为了网络高精度传输 设计的;Unix时间戳是为了操作系统内部简单计算设计的。两者起点不同、精度不同,所以需要转换。
其他的时间戳还有很多,比如:
- Windows 系统时间戳:起点是1601年1月1日,以100纳秒为间隔,存储在64位结构中。
- PTP 时间戳(IEEE 1588 精密时间协议):起点是1970年1月1日(TAI国际原子时,非UTC),精度可达纳秒甚至皮秒级,用于金融交易和5G基站等高精尖领域。
- GPS 时间戳:起点是1980年1月6日,连续计数不含闰秒。
- Mac/Apple Cocoa 时间戳:起点是2001年1月1日,以秒为单位。
所以在windows上用Python (是一门跨平台语言,在底层实现CPython中做了转换)写NTP相关的代码时是转化成Unix时间戳,到 C/C++ (调用Windows API)转化成Windows系统时间戳。
NTP-Unix转换公式:
NTP时间戳 = (Unix时间戳 + 2208988800) << 32 | 小数部分
Unix时间戳 = (NTP时间戳 >> 32) -- 2208988800
NTP的2036年溢出问题
NTP的2036年溢出问题的解决(v4版)并没有修改数据包结构(为了向后兼容,老设备依然能解析)。它的解决逻辑非常巧妙,记录在 RFC 5905 中(软件层面的"纪元推断"和加法修正),可以参考链接如下:
https://www.rfc-editor.org/info/rfc5905/#page-13
1.定义新的纪元:
当 2036 年到来,32位秒数溢出归零后,NTP 并不认为这是 1900 年,而是定义这是一个新的纪元。(有一种三体的感觉)
第一个纪元:1900年 - 2036年
第二个纪元:2036年 - 2172年
第三个纪元:2172年 - 2308年
以此类推,每 136 年一个纪元。
2.客户端如何推断现在是哪个纪元?
NTP 协议规定:客户端和服务器在通信时,默认双方处于同一个纪元。客户端在发包时,知道自己本地的大致时间(比如客户端知道现在是 2036 年 5 月)。当它收到服务器发来的时间戳(秒数部分是一个很小的数字,比如 1000万秒)时,客户端会这样算:
"现在是 2036 年,处于第二个纪元。服务器发来的小数字,一定是第二个纪元里的时间。"
于是客户端自动把这个小数字加上 2^32秒的偏移量,还原出正确的时间。
3.跨纪元过渡期怎么办?(2035年-2037年)
但如果在 2035 年,客户端(还在第一个纪元末尾)向服务器请求,但服务器因为某种原因已经翻到了第二个纪元(返回了很小的秒数),客户端怎么判断?
规则: 如果客户端收到的时间戳,比客户端自己当前的时间小很多(比如相差超过 68 年),客户端就会自动给收到的时间戳加上一个 2^32(约136年),看看加上之后是不是合理。如果加上之后刚好和本地时间接近,就说明服务器已经进入了下一个纪元。
NTP采用UDP协议
NTP传输采用的是UDP协议,因为NTP 不需要 TCP 的"可靠性"和"顺序性",它需要的是 UDP 极低的协议开销和网络延迟以及"无状态的并发处理能力"。对于时间同步来说,"快速拿到一个最新的时间快照"比"确保每一个包都送达"重要得多。