【得捷电子Follow me第4期】进阶任务:从NTP服务器同步时间
[复制链接]
本帖最后由 HonestQiao 于 2024-1-14 21:09 编辑
进阶任务:从NTP服务器(注意数据交互格式的解析)同步时间,获取时间送显示屏(串口)显示。
NTP是从时间协议(Time Protocol)和ICMP时间戳报文(ICMP TimeStamp Message)演变而来,在准确性和健壮性方面进行了特殊的设计,理论上精度可达十亿分之一秒。
NTP协议应用于分布式时间服务器和客户端之间,实现客户端和服务器的时间同步,从而使网络内所有设备的时钟基本保持一致。
NTP协议是基于UDP进行传输的,使用端口号为123。
一、NTP报文了解
NTP报文的数据格式具体如下:
NTP报文格式如上图所示,它的字段含义参考如下:
- LI( 闰秒标识器 ),占用2bits,值为“11”时表示告警状态,时钟未被同步;其他值时NTP本身不做处理
- 1:no warning 未告警
- 2:last minute of the day has 61 seconds 最后一分钟有61秒
- 3:last minute of the day has 59 seconds 最后一分钟有59秒
- 4:unknown (clock unsynchronized) 未知(时钟未同步)
- VN( 版本号 ),占用3bits,表示NTP的版本号,目前最新版本为4.
- Mode( 模式 ),占用3bits,表示NTP的工作模式;不同的值所表示的含义分区是:
- 0:reserved 未定义
- 1:symmetric active 表示主动对等模式
- 2:symmetric passive 表示被动对等模式
- 3:client 表示客户模式
- 4:server 表示服务器模式
- 5:broadcast 表示广播模式或组播模式
- 6:NTP control message 表示此报文NTP控制报文
- 7:reserved for private use 预留给内部使用
- Stratum( 层 ),系统时钟的层数,占用8bits;
- 0 :unspecified or invalid 未指定或无效
- 1 :primary server (e.g., equipped with a GPS receiver) 主服务器
- 2-15 :secondary server (via NTP) 辅助服务器
- 16 :unsynchronized 不同步
- 17-255:reserved 保留
- Poll( 轮询间隔 ),占用8bits,表示两个NTP报文之间的最大时间间隔,以log2秒为单位,默认最小和最大的轮询间隔为6和10。
- Precision( 精度 ),占用8bits,表示系统时钟的精度,以log2秒为单位
- Root Delay( 根时延 ),占用8bits,表示在到参考时钟的总往返的时延
- Root Dispersion( 根离散 ),占用8bits,表示到参考时钟的总色散
- Reference Identifier( 参考时钟标识符),占用32bits,用来标识特殊的参考源
- Reference Timestamp( 参考时间戳 ),占用64bits,系统时钟最后一次被修改的本地时间
- Origin Timestamp( 原始时间戳 ),占用64bits,NTP请求报文离开发送端时发送端的本地时间
- Receive Timestamp( 接受时间戳 ),占用64bits,NTP请求报文到达接收端的本地时间
- Transmit Timestamp( 传送时间戳 ),占用64bits,NTP应答报文离开应答者时的应答者本地时间
- Destination Timestamp( 目的时间戳 ),NTP应答包从服务器到达客户端的时间,(注:该字段不在标题字段中,它在数据包到达时确定,并在数据包缓冲区数据包结构中体现。)
- Extension Field 2 ( 扩展字段,可变 )
- Key Identifier ( 密钥标识符 ):客户端和服务器用于指定128位MD5哈希值
- dgst( Message Digest,消息摘要 ):NTP数据包头和扩展字段
二、直接socket请求进行NTP通讯
在Arduino的Ethernet库中,提供了一个直接使用Socket进行NTP请求的实例。
在读取到NTP信息后,会使用如下的代码解码:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
从而获取自1900年以来的秒数,在转换到实际的时间信息。
大家感兴趣的话,可以自行研究这个源码。
三、使用NTPClient库来进行时间同步
在Arduino中,使用NTPClient可以很方便的进行时间同步。
首先,需要安装这个库:
然后,在之前配置好网络的基础上,添加下面的代码:
// UDP
#include <EthernetUdp.h>
// NTP
#include <NTPClient.h>
// UDP
EthernetUDP ntpUDP;
// 初始化NTP实例,设置时间服务器、时区偏移秒数、更新时间间隔
NTPClient timeClient(ntpUDP, "cn.pool.ntp.org", 3600*8, 60000);
void setup() {
// 在联网成功以后,添加如下的代码
// 开启NTP服务
Serial.println("ntp Begin");
timeClient.begin();
}
void loop() {
// 更新时间
timeClient.update();
// 输出当前时间
Serial.println(timeClient.getFormattedTime());
// 延时
delay(5000);
}
上述代码的核心,就是 使用 NTPClient timeClient(ntpUDP, "cn.pool.ntp.org", 3600*8, 60000) 设置使用cn.pool.ntp.org同步时间,以及北京地区位+8时区,所以秒数偏移量位3600*8,然后60秒后进行同步,然后启动服务,并在lookp()中,定期更新。
编译运行后,输出如下:
NTP服务同步需要一定的时间,所以在一定时间后,就会更新到了最新的正确时间了。
四、总结
NTP服务是网络应用中,非常重要的服务。
即使是使用了RTC时钟模块的设备,也无法保证时间是完全准确的,因为运行一定时间之后都有偏差。
那么适时通过网络同步修正,就非常必要而且重要了。
这样,可以确保各项网络设备,都在同一个时间进行交互运行,以免时间错乱产生不可预测的结果。
|