NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议。它的用途是把计算机的时钟同步到世界协调时UTC,其精度在局域网内可达0.1ms,在互联网上绝大多数的地方其精度可以达到1-50ms。
它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)进行时间同步,它可以提供高精准度的时间校正,而且可以使用加密确认的方式来防止病毒的协议攻击。
NTP要提供准确的时间,就必须有准确的时间来源,那可以用格林尼治时间吗?答案是否定的。因为格林尼治时间是以地球自转为基础的时间计量系统,但是地球每天的自转是有些不规则的,而且正在缓慢加速,因此,格林尼治时间已经不再被作为标准时间使用。
新的标准时间,是由原子钟报时的国际标准时间UTC(Universal Time Coordinated,世界协调时)。所以NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。有了准确而可靠的的时间源,那这个时间如何传播呢?在NTP中,定义了时间按照服务器的等级传播,按照离外部UTC源远近将所有的服务器归入不同的Stratum(层)中,例如把通过GPS(Global Positioning System,全球定位系统)取得发送标准时间的服务器叫Stratum-1的NTP服务器,而Stratum-2则从Stratum-1获取时间,Stratum-3从Stratum-2获取时间,以此类推,但Stratum层的总数限制在15以内。所有这些服务器在逻辑上形成阶梯式的架构相互连接,而Stratum-1的时间服务器是整个系统的基础
NTP时间同步报文中包含的时间是格林威治时间,是从1900年开始计算的秒数
NTP有两种不同类型的报文,一种是时钟同步报文,另一种是控制报文。控制报文仅用于需要网络管理的场合,它对于时钟同步功能来说并不是必需的,这里不做介绍。
LI | VN | Mode | Stratum | Poll | Precision |
---|---|---|---|---|---|
0-2bit | 3-5bit | 6-8bit | 9-16bit | 17-24bit | 25-32bit |
Root Delay | 32bit | ||||
Root Dispersion | 32bit | ||||
Reference Timestamp | 64bit | ||||
Originate Timestamp | 64bit | ||||
Receive Timestamp | 64bit | ||||
Transmit Timestamp | 64bit | ||||
Authentication (optional) | 64bit |
字段中文名 | 字段英文名 | 值定义 |
---|---|---|
闰秒提示符 | LI | 0 无预告1 最近一分钟有61秒2 最近一分钟有 59秒3 警告状态(时钟未同步) |
版本号 | VN | 这是一个三位的整数可以用于表示NTP版本最新版本为4 |
模式 | Mode | 0 保留 1 对称主动 2 对称被动 3 客户端 4 服务器端 5 广播 6 为NTP控制控制消息 7 为自用保留 |
本地时钟层级 | Stratum | 0 未定义或难以获得 1 主要参考(如无线电时钟钟,校正的原子时钟) 2-255 第二参考(通过NTP或SNTP) |
轮询间隔 | Poll | 这是一个8位有符号整数,用于表示连续消息之间的最大间隔,以最接近2的N次幂来表示。如值为6表示2^6=64。 |
本地时钟经度 | Precision | 这是一个8位有符号整数,用于表示本地时钟精度,以最接近2的N次幂来表示。 |
根延迟 | Root delay | 这是一个32位有符号定点数,表示主要参考源的总往返时延,以秒为单位。该变量可以为正值和负值,具体取决于时间精度和偏移。 |
最大误差偏移 | Root Dispersion | 这是一个32位有符号定点数,表示相对于主参考源的最大误差,以秒为单位,在15和16位之间。通常在该字段中出现的值范围为0到几百毫秒 |
标识特定参考源位串 | Reference Identifier | 一个标识特定参考源的32位位串。在NTP版本3或版本4层级0或层级1服务器的情况下,这是一个4字符ASCII字符串,左对齐并且以0填充到32位。在NTP版本3辅助服务器中,这是参考源的32位IPv4地址。 |
上次设置或更正的本地时间戳 | Reference Timestamp | 这是以64位时间戳格式表示的上次设置或更正的本地时钟时间。 |
时间报文离开本地客户端的时间戳 | Original Timestamp | 这是以64位时间戳格式表示的请求离开客户端的时间。 |
请求到达服务器的时间戳 | Receive Timestamp | 这是以64位时间戳格式表示的请求到达服务器端的时间。 |
应答离开服务器的时间戳 | Transmit Timestamp | 这是以64位时间戳格式表示的应答离开服务器端的时间。 |
认证信息 | Authentication | 认证信息。 |
表1-1 NTP报文英文字段及其中文解释
开始=》构造NTP报文=》向服务器发送报文=》监听服务器的报文回复=》接收服务器的回复=》处理服务器发来的报文并且更新系统时间=》结束
NTP抓取时间的模块的大概流程思路如上所示。
-
构造向服务器发送的NTP报文
-
创建并配置要向服务器发送报文的socket(套接字),使用sockaddr_in结构体配置socket。配置完成之后使用connect函数,建立socket与服务器的连接。建立连接成功之后使用send函数通过socket将其中的报文发送给服务器。
-
将套接字加入到一个fd_set之中然后使用,select函数对其状态进行监听。
-
检测到可读事件后,select函数会返回一个特定的返回值,之后调用recv函数对服务器返回的报文进行接收。
-
成功接收到服务器返回的报文并确认其无误之后,对报文进行解析处理。获得了从服务器获得的时间之后,更新系统的时间完成整个流程。
注:在该模块中,发送一次报文回得来一次服务器的报文回复,所以整个流程需要做成一次性的处理,如果中间的流程出现错误,可以直接退出循环的流程直接开始下一次。
在该模块中中,将NTP报文的构造,以及报文的发送封装进了send_packet函数。
将接收服务器发来的报文,以及将报文解析的工作封装进了get_server_time中。
将更新系统时间封装进了mod_localtime函数之中。
其他的一些准备工作例如一些变量的声明及初始化,socket的创建,连接服务器connect,监听select,写在了ntp_get_time_run主流程之中。
异常处理部分将整个流程封装成了一个ntp_get_time_run中,如果同一个错误出现五次,那么调用回调函数向调用者返回一个整形的错误代码 -1.
NTP时间获取模块的API函数提供了了两个接口。
表头文件 | #include “ntp_time” |
---|---|
定义函数 | int ntp_get_time(void(*ntpCall),int) |
函数说明 | 其中第一个参数为调用者提供了回调函数的接口,调用者可以通过将自己的方法函数,获得NTP模块的工作状况,返回一个int 型的整数,其中获取时间失败为-1,成功获取时间为1.。int in_tryTime为调用者提供了一个能够控制NTP模块运行次数的接口,调用者可以通过设定int型次数,控制NTP模块的运行次数,如果输入0则模块不会结束,以一定时间一直更新系统的时间。 |
返回值 | 调用成功返回0,出现错误返回-1 |