汽车电子 -- NTP时间同步

当前对于ADAS领域的时间同步大体可以分为如下几种,见下图:
在这里插入图片描述
因此,基于上述几大常见的时间同步方案的区别,我们需要从我们实际的应用场景,精度要求,系统设计等方面综合分析来选择合适的时间同步方案。

GNSS是总结过了的。
GNSS,参看:STM32开发 – UTC、UNIX时间戳、北京时间之间的转换

下面主要介绍 NTP 时间同步方案。

一、NTP时间同步

参看:什么是NTP?
参看:NTP协议详解及C语言实现

网络时间协议NTP(Network Time Protocol)是TCP/IP协议族里面的一个应用层协议,用来使客户端和服务器之间进行时钟同步,提供高精准度的时间校正。NTP服务器从权威时钟源(例如原子钟、GPS)接收精确的协调世界时UTC,客户端再从服务器请求和接收时间。

1、为什么时钟同步很重要?

出于诸多原因,精确的时间对于网络至关重要,比如:

  • 网络管理:从不同网络设备采集来的日志信息进行分析时,需要以时间作为参照依据。如果不同设备上的系统时间不一致,会因先后顺序等问题给故障定位带来障碍。
  • 计费系统:计费业务对于时间尤其敏感,要求所有设备的时间保持一致,否则会引起计费不准确,导致用户质疑、投诉等。
  • 协同处理:多个系统协同处理同一个复杂事件,为保证正确的执行顺序,多个系统必须参考同一时钟。
  • 系统时间:某些应用或服务需要准确的时间来标记用户登录、交易等操作信息,确保可追溯记录。
    因此有一个统一的标准时间对于网络而言意义重大。

NTP就是用来使网络中的各个主机时钟同步的一种协议,他把主机的时钟同步到协调世界时UTC,其精度在LAN网络内可达1毫秒内,在WAN网络上可以达到几十毫秒内。

2、NTP同步原理

NTP最典型的授时方式是Client/Server方式,如下图所示。
在这里插入图片描述
NTP同步原理

    1. 客户端首先向服务端发送一个NTP请求报文,其中包含了该报文离开客户端的时间戳t1;
    1. NTP请求报文到达NTP服务器,此时NTP服务器的时刻为t2。当服务端接收到该报文时,NTP服务器处理之后,于t3时刻发出NTP应答报文。该应答报文中携带报文离开NTP客户端时的时间戳t1、到达NTP服务器时的时间戳t2、离开NTP服务器时的时间戳t3
    1. 客户端在接收到响应报文时,记录报文返回的时间戳t4

客户端用上述4个时间戳参数就能够计算出2个关键参数:
NTP报文从客户端到服务器的往返延迟delay。
在这里插入图片描述
客户端与服务端之间的时间差offset。
根据方程组:
在这里插入图片描述
可以解得时间差为:
在这里插入图片描述
NTP客户端根据计算得到的offset来调整自己的时钟,实现与NTP服务器的时钟同步。

示例:
在这里插入图片描述

3、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 位的小数部分。容易发现,日期戳能表示的时间范围更广,精度也更高,它的跨度长于宇宙的年龄,精度小于光通过一个原子的时间。

时间戳常用于通信时的数据包中,而日期戳一般只在实现内部使用。

扩展:

几个时间相关的概念

  • GMT时间: Greenwich Mean Time,格林尼治平时,又称格林尼治平均时间或格林尼治标准时间。是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间。
    GMT存在较大误差,因此现在已不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)
  • UTC时间: Universal Time Coordinated,中文名称:世界标准时间或世界协调时。
    UTC时间可以理解为全世界都公用的一个时间。它实际上反映了一种约定,即为全世界所认可的一个统一时间,而不是某特定地区的时间。
    中国人常用的北京时间比UTC时间快8个小时。也即UTC时间凌晨0点时,北京时间已经是早上8点,这就是为啥全世界人往往不直接用UTC时间计时原因。
  • CST时间: China Standard Time,即中国标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。
  • UNIX时间戳(timestamp): 计算机中的UNIX时间戳,是以GMT/UTC时间1970-01-01T00:00:00为起点,到当前具体时间的秒数(不考虑闰秒)。这样做的目的,主要是通过“整数计算”来简化计算机对时间操作的复杂度。

UTC时间和UNIX时间戳转换工具
在这里插入图片描述
这部分之前都写了好几篇文章了。
参看:STM32开发 – UTC、UNIX时间戳、北京时间之间的转换

参看:DSP学习-- UTC转UNIX时间戳

参看:DSP学习 – GPS时间校准电脑系统时间

4、NTP报文格式

NTP协议的标准,RFC1350协议
下载:Index of /rfc
参看:NTP

(1)报文结构

在这里插入图片描述

(2)字段含义

  • Leap Indicator (LI): 跳跃指示器,占用2bits,表示即将插入/删除在当天的最后一分钟,比特0和比特1分别编码如下:
    00:no warning 未告警
    01:last minute of the day has 61 seconds 最后一分钟有61秒
    10:last minute of the day has 59 seconds 最后一分钟有59秒
    11: alarm condition (clock not synchronized)报警条件(时钟未同步)

  • Version Number(VN):版本号,占用3bits,表示NTP版本号,目前为3,即011。

  • Mode:模式,占用3bits,其值定义如下:
    000 reserved 保留
    001 symmetric active 表示主动对等模式
    010 symmetric passive 表示被动对等模式
    011 client 表示客户模式
    100 server 表示服务器模式
    101 broadcast 表示广播模式
    110 reserved for NTP control message (see Appendix B) 预留给NTP控制报文
    111 reserved for private use 预留给内部使用

  • Stratum( 层 ),占用8bits,系统时钟的层数。层数为1的时钟准确度最高,从1到15依次递减。
    0 unspecified 未指定
    1 primary reference (e.g., radio clock) 主服务器
    2-255 secondary reference (via NTP) 辅助服务器

  • Poll Interval( 轮询间隔 ),占用8bits,表示连续消息之间的最大间隔,以秒为单位,以2的次幂为单位。该字段的取值范围从NTP开始。最小值MINPOLL。最大值MAXPOLL。
    Min Polling Interval NTP.MINPOLL 6 (64 sec)
    Max Polling Interval NTP.MAXPOLL 10 (1024 sec)

  • Precision( 精度 ),占用8bits,表示本地时钟的精度,以秒为单位,以2的次幂为单位。

  • Root Delay( 根时延 ),占用32bits,指示到主参考源的总往返延迟,以秒为单位,分数点在15和16位之间。请注意,这个变量可以同时接受正值和负值,这取决于时钟的精度和倾斜度。

  • Root Dispersion( 根离散 ),占用32bits,表示相对于主参考源的最大误差,以秒为单位,分数点在位15和16之间。只有大于零的正值才有可能出现。

  • Reference Clock Identifier:( 参考时钟标识符),占用32bits。在层0(未指定)或层1(主引用)的情况下,这是一个四字节、左对齐、填充零的ASCII字符串。虽然没有作为NTP规范的一部分列举,但建议以下ASCII标识符:

    在层2及以上(辅助参考)的情况下,这是主参考主机的四字节Internet地址。

  • Reference Timestamp (参考时间戳):占用64bits,本地时钟最后一次被设定或更新的时间。如果值为0表示本地时钟从未被同步过。

  • Originate Timestamp(起始时间戳):占用64bits,这是请求离开客户端主机前往服务主机的本地时间。

  • Receive Timestamp(接收时间戳):占用64bits,这是请求到达服务主机的本地时间。

  • Transmit Timestamp(传输时间戳):占用64bits,这是应答从服务主机发送到客户主机的本地时间。

  • Authenticator(可选):(可选)验证信息。

NTP客户端发送报文示例: 1B 00 04 FA 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D9 FD 84 95 94 F8 59 7C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
解析:
第1个字节:1B(0001 1011 )
00 即,LI(跳跃指示器) 00:no warning 未告警
011 即,VN:版本号为3
011 即,Mode:client 表示客户模式
第2个字节:00
00 即,Stratum:0 unspecified 未指定
第3个字节:04
04 即,Poll( 轮询间隔 )两个NTP报文之间的时间间隔,04就是2^4 = 16秒
第4个字节:FA
FA 即,Precision( 精度 ),本地时钟的精度。
第5~8字节:00 01 00 00
00 01 00 00 即,Root Delay( 根时延 )主参考源的总往返延迟,二进制为00010000000000000000,即1秒
第9~12字节:00 01 00 00
00 01 00 00 即,Root Dispersion( 根离散 )
第13~16字节:00 00 00 00
00 00 00 00 即,Reference Clock Identifier:( 参考时钟标识符)
第17~24字节:00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 即,Reference Timestamp (参考时间戳)
第25~32字节: D9 FD 84 95 94 F8 59 7C
D9 FD 84 95 94 F8 59 7C 即,Originate Timestamp(起始时间戳)
第33~40字节:00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 即,Receive Timestamp(接收时间戳)
第41~48字节:00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 即,Transmit Timestamp(传输时间戳)

一共48个字节。
其中,起始时间戳 D9 FD 84 95 94 F8 59 7C
秒的计算:先处理MSW高位32bit“D9 FD 84 95”,将其转换为10进制3657270421,由于NTP时间和UTC时间起始不同,需要将该时间减少70年(1900年到1970年)2208988800(0x83AA7E80),则为1448281621,使用UTC转换器为2015-11-23 12:27:01 UTC(格林威治时间),2015-11-23 20:27:01 UTC+8(北京时间 UTC+8);
毫秒计算:然后再处理LSW低位32bit “94 F8 59 7C”,在处理LSW之前先要了解1LSW=232ps是怎么来的,1 second(秒) =1,000,000,000,000 picoseconds(皮秒),这个值很大,而2 ^ 32=4294967296,很明显用32bit无法精确到1 picoseconds,那就尽力而为,于是自然就把1,000,000,000,000 picoseconds劈成2^32份:
1,000,000,000,000/(2^32)=232.8306436,即1LSW=232ps;
故先将LSW乘以232转为ps,然后ps除以10^6就得到us了,于是有:usec=lsw*232/1000000。
综上所述,故NTP时间戳“D9 FD 84 95 94 F8 59 7C”转换成UTC时间为:2015-11-23 20:27:01.579838 UTC+8(北京时间)

5、Windows下搭建NTP服务器

使用组合键WIN + R 启动运行窗口,在打开的窗口中输入regedit,点击确定按钮。
在这里插入图片描述
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config,找到Config目录,双击Config目录下的AnnounceFlags
将如图值改为5
在这里插入图片描述
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer,双击NtpServer下的Enabled文件
将值改为1
在这里插入图片描述
运行–打开服务 输入以下命令 services.msc
在这里插入图片描述
一定要确保win32time服务的启动
在这里插入图片描述
在这里插入图片描述
以管理员身份运行—CMD :输入net stop w32time停止服务,再输入net start w32time启动服务
在这里插入图片描述
使用网络调试助手向NTP服务器发送如下数据(HEX):
时间信息的传输都使用UDP接口。服务器端口默认为123。
在这里插入图片描述

NTP服务器应答报文示例: 1C 01 04 E9 00 00 00 00 00 0A 00 9D 4C 4F 43 4C E9 2B F3 34 F7 79 20 7D 00 00 00 00 00 00 00 00 E9 2B F4 04 8B B2 3C 27 E9 2B F4 04 8B B2 87 A7
解析:
第1个字节:1C(0001 1100 )
00 即,LI(跳跃指示器) 00:no warning 未告警
011 即,VN:版本号为3
100 即,Mode:表示服务器模式
第2个字节:01
01 即,Stratum:主服务器
第3个字节:04
04 即,Poll( 轮询间隔 )两个NTP报文之间的时间间隔,04就是2^4 = 16秒
第4个字节:E9
E9 即,Precision( 精度 ),本地时钟的精度。
第5~8字节:00 00 00 00
00 00 00 00 即,Root Delay( 根时延 )主参考源的总往返延迟
第9~12字节:00 0A 00 9D
00 0A 00 9D 即,Root Dispersion( 根离散 )
第13~16字节:4C 4F 43 4C
4C 4F 43 4C 即,Reference Clock Identifier:( 参考时钟标识符)
第17~24字节:E9 2B F3 34 F7 79 20 7D
E9 2B F3 34 F7 79 20 7D 即,Reference Timestamp (参考时间戳)
第25~32字节: 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 即,Originate Timestamp(起始时间戳)
第33~40字节:E9 2B F4 04 8B B2 3C 27
E9 2B F4 04 8B B2 3C 27 即,Receive Timestamp(接收时间戳)
第41~48字节:E9 2B F4 04 8B B2 87 A7
E9 2B F4 04 8B B2 87 A7 即,Transmit Timestamp(传输时间戳)

转换后的参考时钟标识符: 2023-12-19 18:47:16.963243
转换后的接收时间戳为: 2023-12-19 18:50:44.543741
转换后的传输时间戳为: 2023-12-19 18:50:44.543746
能跟上图的网络调试助手的时间对应上的。

6、Wireshark数据包捕获

使用网络调试助手,周期性发送NTP客户端报文。
在这里插入图片描述
打开Wireshark捕获数据:
在这里插入图片描述
可以看到NTP的客户端和服务器
在这里插入图片描述
其中,客户端向服务器发送时钟同步报文,报文中的Mode字段设置为3(客户模式)。
在这里插入图片描述

服务器端收到报文后会自动工作在服务器模式,并发送应答报文,报文中的Mode字段设置为4(服务器模式)。
在这里插入图片描述

7、C/C++代码实现

参看:使用NTP协议获取网络时间戳(C/C++实现)

NTP_Time.h

按照NTP协议RFC1350,创建的NTP通信相关的数据结构体:

//返回值
#define X_FALSE           0
#define X_TRUE            1
#define X_NULL            0
#define X_ERR_OK          0
#define X_ERR_UNKNOW      (-1)

#define X_INVALID_SOCKFD  ((x_sockfd_t)~0)
#define NTP_PORT   123  ///< NTP 专用端口号
#define JAN_1970     0x83AA7E80             ///< 1900 ~ 1970 年之间的时间 秒数
#define NS100_1970   116444736000000000LL   ///< 1601 ~ 1970 年之间的时间 百纳秒数
#define NTP_PROTOCOL_LEN 48


//NTP 时间戳
typedef struct x_ntp_timestamp_t
{
    x_uint32_t  xut_seconds;    ///< 从 1900年至今所经过的秒数
    x_uint32_t  xut_fraction;   ///< 小数部份,单位是微秒数的4294.967296( = 2^32 / 10^6 )倍
} x_ntp_timestamp_t;

//对系统的 timeval 结构体进行重定义结构体。
typedef struct x_ntp_timeval_t
{
    x_long_t    tv_sec;    ///< 秒
    x_long_t    tv_usec;   ///< 微秒
} x_ntp_timeval_t;

//时间描述信息结构体。
typedef struct x_ntp_time_context_t
{
    x_uint32_t   xut_year   : 16;  ///< 年
    x_uint32_t   xut_month  :  6;  ///< 月
    x_uint32_t   xut_day    :  6;  ///< 日
    x_uint32_t   xut_week   :  4;  ///< 周几
    x_uint32_t   xut_hour   :  6;  ///< 时
    x_uint32_t   xut_minute :  6;  ///< 分
    x_uint32_t   xut_second :  6;  ///< 秒
    x_uint32_t   xut_msec   : 14;  ///< 毫秒
} x_ntp_time_context_t;

//NTP工作模式的相关枚举值
typedef enum em_ntp_mode_t
{
    ntp_mode_unknow     = 0,  ///< 未定义
    ntp_mode_initiative = 1,  ///< 主动对等体模式
    ntp_mode_passive    = 2,  ///< 被动对等体模式
    ntp_mode_client     = 3,  ///< 客户端模式
    ntp_mode_server     = 4,  ///< 服务器模式
    ntp_mode_broadcast  = 5,  ///< 广播模式或组播模式
    ntp_mode_control    = 6,  ///< 报文为 NTP 控制报文
    ntp_mode_reserved   = 7,  ///< 预留给内部使用
} em_ntp_mode_t;

//NTP 报文格式
typedef struct x_ntp_packet_t
{
    x_uchar_t         xct_li_ver_mode;      ///< 2 bits,飞跃指示器;3 bits,版本号;3 bits,NTP工作模式(参看 em_ntp_mode_t 相关枚举值)
    x_uchar_t         xct_stratum    ;      ///< 系统时钟的层数,取值范围为1~16,它定义了时钟的准确度。层数为1的时钟准确度最高,准确度从1到16依次递减,层数为16的时钟处于未同步状态,不能作为参考时钟
    x_uchar_t         xct_poll       ;      ///< 轮询时间,即两个连续NTP报文之间的时间间隔
    x_uchar_t         xct_percision  ;      ///< 系统时钟的精度
    x_uint32_t        xut_root_delay     ;  ///< 本地到主参考时钟源的往返时间
    x_uint32_t        xut_root_dispersion;  ///< 系统时钟相对于主参考时钟的最大误差
    x_uint32_t        xut_ref_indentifier;  ///< 参考时钟源的标识
    x_ntp_timestamp_t xtmst_reference;      ///< 系统时钟最后一次被设定或更新的时间(应答完成后,用于存储 T1)
    x_ntp_timestamp_t xtmst_originate;      ///< NTP请求报文离开发送端时发送端的本地时间(应答完成后,用于存储 T4)
    x_ntp_timestamp_t xtmst_receive  ;      ///< NTP请求报文到达接收端时接收端的本地时间(应答完成后,用于存储 T2)
    x_ntp_timestamp_t xtmst_transmit ;      ///< NTP应答报文离开应答者时应答者的本地时间(应答完成后,用于存储 T3)
} x_ntp_packet_t;

x_uint64_t ntp_gettimevalue(void);
x_void_t ntp_gettimeofday(x_ntp_timeval_t * xtm_value);
x_uint64_t ntp_time_value(x_ntp_time_context_t * xtm_context);
x_bool_t ntp_tmctxt_bv(x_uint64_t xut_time, x_ntp_time_context_t * xtm_context);

NTP_Time.c

/**********************************************************/
/**
 * @brief 将 x_ntp_timeval_t 转换为 x_ntp_timestamp_t 。
 */
static inline x_void_t ntp_timeval_to_timestamp(x_ntp_timestamp_t * xtm_timestamp, const x_ntp_timeval_t * const xtm_timeval)
{
    const x_lfloat_t xlft_frac_per_ms = 4.294967296E6;  // 2^32 / 1000
    xtm_timestamp->xut_seconds  = (x_uint32_t)(xtm_timeval->tv_sec  + JAN_1970);
    xtm_timestamp->xut_fraction = (x_uint32_t)(xtm_timeval->tv_usec / 1000.0 * xlft_frac_per_ms);
}

/**
 * @brief 将 x_ntp_timeval_t 转换为 x_ntp_timestamp_t 。
 */
static inline x_void_t ntp_timestamp_to_timeval(x_ntp_timeval_t * xtm_timeval, const x_ntp_timestamp_t * const xtm_timestamp)
{
    const x_lfloat_t xlft_frac_per_ms = 4.294967296E6;  // 2^32 / 1000

    if (xtm_timestamp->xut_seconds >= JAN_1970)
    {
        xtm_timeval->tv_sec  = (x_long_t)(xtm_timestamp->xut_seconds - JAN_1970);
        xtm_timeval->tv_usec = (x_long_t)(xtm_timestamp->xut_fraction / xlft_frac_per_ms * 1000.0);
    }
    else
    {
        xtm_timeval->tv_sec  = 0;
        xtm_timeval->tv_usec = 0;
    }
}

/**
 * @brief 将 x_ntp_timeval_t 转换成 100纳秒为单位的值。
 */
static inline x_uint64_t ntp_timeval_ns100(const x_ntp_timeval_t * const xtm_timeval)
{
    return (10000000ULL * xtm_timeval->tv_sec + 10ULL * xtm_timeval->tv_usec);
}

/**
 * @brief 将 x_ntp_timeval_t 转换成 毫秒值。
 */
static inline x_uint64_t ntp_timeval_ms(const x_ntp_timeval_t * const xtm_timeval)
{
    return (1000ULL * xtm_timeval->tv_sec + (x_uint64_t)(xtm_timeval->tv_usec / 1000.0 + 0.5));
}

/**
 * @brief 将 x_ntp_timestamp_t 转换成 100纳秒为单位的值。
 */
static inline x_uint64_t ntp_timestamp_ns100(const x_ntp_timestamp_t * const xtm_timestamp)
{
    x_ntp_timeval_t xmt_timeval;
    ntp_timestamp_to_timeval(&xmt_timeval, xtm_timestamp);
    return ntp_timeval_ns100(&xmt_timeval);
}

/**
 * @brief 将 x_ntp_timestamp_t 转换成 毫秒值。
 */
static inline x_uint64_t ntp_timestamp_ms(const x_ntp_timestamp_t * const xtm_timestamp)
{
    x_ntp_timeval_t xmt_timeval;
    ntp_timestamp_to_timeval(&xmt_timeval, xtm_timestamp);
    return ntp_timeval_ms(&xmt_timeval);
}

x_uint32_t ntohl(x_uint32_t A){
	x_uint32_t ret;
	ret = ((((x_uint32_t)(A) & 0xff000000) >> 24) |  (((x_uint32_t)(A) & 0x00ff0000) >> 8) | (((x_uint32_t)(A) & 0x0000ff00) << 8) |  (((x_uint32_t)(A) & 0x000000ff) << 24));
	return ret;
}

x_uint32_t htonl(x_uint32_t A){
	x_uint32_t ret;
	ret = ntohl(A);
	return ret;
}

//获取时间戳    记录时间戳 + 当前tick - 根tick
uint64_t get_update_time(void)
{
	uint64_t ret_time = 0;
	ret_time = xTaskGetTickCount();
	ret_time = ret_time + Bd_update_time - Bd_base_time;
	return ret_time;
}

void self_gettimeval(struct timeval *time){
	uint64_t tmp_time;
	tmp_time = get_update_time();
	time->tv_sec = tmp_time/1000ULL;
	time->tv_usec = (tmp_time%1000ULL)*1000ULL;
}

/**
 * @brief 获取当前系统的 时间值(以 100纳秒 为单位,1970年1月1日到现在的时间)。
 */
x_uint64_t ntp_gettimevalue(void)
{
#ifdef _MSC_VER
    FILETIME       xtime_file;
    ULARGE_INTEGER xtime_value;

    GetSystemTimeAsFileTime(&xtime_file);
    xtime_value.LowPart  = xtime_file.dwLowDateTime;
    xtime_value.HighPart = xtime_file.dwHighDateTime;

    return (x_uint64_t)(xtime_value.QuadPart - NS100_1970);
#else // !_MSC_VER
    struct timeval tmval;
    self_gettimeval(&tmval);

    return (10000000ULL * tmval.tv_sec + 10ULL * tmval.tv_usec);
#endif // _MSC_VER
}

/**
 * @brief 获取当前系统的 timeval 值(1970年1月1日到现在的时间)。
 */
x_void_t ntp_gettimeofday(x_ntp_timeval_t * xtm_value)
{
#ifdef _MSC_VER
    FILETIME       xtime_file;
    ULARGE_INTEGER xtime_value;

    GetSystemTimeAsFileTime(&xtime_file);
    xtime_value.LowPart  = xtime_file.dwLowDateTime;
    xtime_value.HighPart = xtime_file.dwHighDateTime;

    xtm_value->tv_sec  = (x_long_t)((xtime_value.QuadPart - NS100_1970) / 10000000LL); // 1970年以来的秒数
    xtm_value->tv_usec = (x_long_t)((xtime_value.QuadPart / 10LL      ) % 1000000LL ); // 微秒
#else // !_MSC_VER
    struct timeval tmval;
    self_gettimeval(&tmval);

    xtm_value->tv_sec  = tmval.tv_sec ;
    xtm_value->tv_usec = tmval.tv_usec;
#endif // _MSC_VER
}

/**
 * @brief 将 x_ntp_time_context_t 转换为 以 100纳秒
 *        为单位的时间值(1970年1月1日到现在的时间)。
 */
x_uint64_t ntp_time_value(x_ntp_time_context_t * xtm_context)
{
    x_uint64_t xut_time = 0ULL;

#if 0
    if ((xtm_context->xut_year   < 1970) ||
        (xtm_context->xut_month  <    1) || (xtm_context->xut_month > 12) ||
        (xtm_context->xut_day    <    1) || (xtm_context->xut_day   > 31) ||
        (xtm_context->xut_hour   >   23) ||
        (xtm_context->xut_minute >   59) ||
        (xtm_context->xut_second >   59) ||
        (xtm_context->xut_msec   >  999))
    {
        return xut_time;
    }
#endif

#ifdef _MSC_VER
    ULARGE_INTEGER xtime_value;
    FILETIME       xtime_sysfile;
    FILETIME       xtime_locfile;
    SYSTEMTIME     xtime_system;

    xtime_system.wYear         = xtm_context->xut_year  ;
    xtime_system.wMonth        = xtm_context->xut_month ;
    xtime_system.wDay          = xtm_context->xut_day   ;
    xtime_system.wDayOfWeek    = xtm_context->xut_week  ;
    xtime_system.wHour         = xtm_context->xut_hour  ;
    xtime_system.wMinute       = xtm_context->xut_minute;
    xtime_system.wSecond       = xtm_context->xut_second;
    xtime_system.wMilliseconds = xtm_context->xut_msec  ;

    if (SystemTimeToFileTime(&xtime_system, &xtime_locfile))
    {
        if (LocalFileTimeToFileTime(&xtime_locfile, &xtime_sysfile))
        {
            xtime_value.LowPart  = xtime_sysfile.dwLowDateTime ;
            xtime_value.HighPart = xtime_sysfile.dwHighDateTime;
            xut_time = xtime_value.QuadPart - NS100_1970;
        }
    }
#else // !_MSC_VER
    struct tm       xtm_system;
    x_ntp_timeval_t xtm_value;

    xtm_system.tm_sec   = xtm_context->xut_second;
    xtm_system.tm_min   = xtm_context->xut_minute;
    xtm_system.tm_hour  = xtm_context->xut_hour  ;
    xtm_system.tm_mday  = xtm_context->xut_day   ;
    xtm_system.tm_mon   = xtm_context->xut_month - 1   ;
    xtm_system.tm_year  = xtm_context->xut_year  - 1900;
    xtm_system.tm_wday  = 0;
    xtm_system.tm_yday  = 0;
    xtm_system.tm_isdst = 0;

    xtm_value.tv_sec  = mktime(&xtm_system);
    xtm_value.tv_usec = xtm_context->xut_msec * 1000;
    if (-1 != xtm_value.tv_sec)
    {
        xut_time = ntp_timeval_ns100(&xtm_value);
    }
#endif // _MSC_VER

    return xut_time;
}

/**
 * @brief 转换(以 100纳秒 为单位的)时间值(1970年1月1日到现在的时间)
 *        为具体的时间描述信息(即 x_ntp_time_context_t)。
 *
 * @param [in ] xut_time    : 时间值(1970年1月1日到现在的时间)。
 * @param [out] xtm_context : 操作成功返回的时间描述信息。
 *
 * @return x_bool_t
 *         - 成功,返回 X_TRUE;
 *         - 失败,返回 X_FALSE。
 */
x_bool_t ntp_tmctxt_bv(x_uint64_t xut_time, x_ntp_time_context_t * xtm_context)
{
#ifdef _MSC_VER
    ULARGE_INTEGER xtime_value;
    FILETIME       xtime_sysfile;
    FILETIME       xtime_locfile;
    SYSTEMTIME     xtime_system;

    if (X_NULL == xtm_context)
    {
        return X_FALSE;
    }

    xtime_value.QuadPart = xut_time + NS100_1970;
    xtime_sysfile.dwLowDateTime  = xtime_value.LowPart;
    xtime_sysfile.dwHighDateTime = xtime_value.HighPart;
    if (!FileTimeToLocalFileTime(&xtime_sysfile, &xtime_locfile))
    {
        return X_FALSE;
    }

    if (!FileTimeToSystemTime(&xtime_locfile, &xtime_system))
    {
        return X_FALSE;
    }

    xtm_context->xut_year   = xtime_system.wYear        ;
    xtm_context->xut_month  = xtime_system.wMonth       ;
    xtm_context->xut_day    = xtime_system.wDay         ;
    xtm_context->xut_week   = xtime_system.wDayOfWeek   ;
    xtm_context->xut_hour   = xtime_system.wHour        ;
    xtm_context->xut_minute = xtime_system.wMinute      ;
    xtm_context->xut_second = xtime_system.wSecond      ;
    xtm_context->xut_msec   = xtime_system.wMilliseconds;
#else // !_MSC_VER
    struct tm xtm_system;
    time_t xtm_time = (time_t)(xut_time / 10000000ULL);
    localtime_r(&xtm_time, &xtm_system);

    xtm_context->xut_year   = xtm_system.tm_year + 1900;
    xtm_context->xut_month  = xtm_system.tm_mon  + 1   ;
    xtm_context->xut_day    = xtm_system.tm_mday       ;
    xtm_context->xut_week   = xtm_system.tm_wday       ;
    xtm_context->xut_hour   = xtm_system.tm_hour       ;
    xtm_context->xut_minute = xtm_system.tm_min        ;
    xtm_context->xut_second = xtm_system.tm_sec        ;
    xtm_context->xut_msec   = (x_uint32_t)((xut_time % 10000000ULL) / 10000L);
#endif // _MSC_VER

    return X_TRUE;
}

/**
 * @brief 初始化 NTP 的请求数据包。
 */
static x_void_t ntp_init_request_packet(x_ntp_packet_t * xnpt_dptr)
{
    const x_uchar_t xct_leap_indicator = 0;
    const x_uchar_t xct_ntp_version    = 3;
    const x_uchar_t xct_ntp_mode       = ntp_mode_client;

    xnpt_dptr->xct_li_ver_mode = (xct_leap_indicator << 6) | (xct_ntp_version << 3) | (xct_ntp_mode << 0);
    xnpt_dptr->xct_stratum     = 0;
    xnpt_dptr->xct_poll        = 4;
    xnpt_dptr->xct_percision   = ((-6) & 0xFF);

    xnpt_dptr->xut_root_delay      = (1 << 16);
    xnpt_dptr->xut_root_dispersion = (1 << 16);
    xnpt_dptr->xut_ref_indentifier = 0;

    xnpt_dptr->xtmst_reference.xut_seconds  = 0;
    xnpt_dptr->xtmst_reference.xut_fraction = 0;
    xnpt_dptr->xtmst_originate.xut_seconds  = 0;
    xnpt_dptr->xtmst_originate.xut_fraction = 0;
    xnpt_dptr->xtmst_receive  .xut_seconds  = 0;
    xnpt_dptr->xtmst_receive  .xut_fraction = 0;
    xnpt_dptr->xtmst_transmit .xut_seconds  = 0;
    xnpt_dptr->xtmst_transmit .xut_fraction = 0;
}

/**
 * @brief 将 x_ntp_packet_t 中的 网络字节序 字段转换为 主机字节序。
 */
static x_void_t ntp_ntoh_packet(x_ntp_packet_t * xnpt_nptr)
{
#if 0
    xnpt_nptr->xct_li_ver_mode = xnpt_nptr->xct_li_ver_mode;
    xnpt_nptr->xct_stratum     = xnpt_nptr->xct_stratum    ;
    xnpt_nptr->xct_poll        = xnpt_nptr->xct_poll       ;
    xnpt_nptr->xct_percision   = xnpt_nptr->xct_percision  ;
#endif
    xnpt_nptr->xut_root_delay               = ntohl(xnpt_nptr->xut_root_delay              );
    xnpt_nptr->xut_root_dispersion          = ntohl(xnpt_nptr->xut_root_dispersion         );
    xnpt_nptr->xut_ref_indentifier          = ntohl(xnpt_nptr->xut_ref_indentifier         );
    xnpt_nptr->xtmst_reference.xut_seconds  = ntohl(xnpt_nptr->xtmst_reference.xut_seconds );
    xnpt_nptr->xtmst_reference.xut_fraction = ntohl(xnpt_nptr->xtmst_reference.xut_fraction);
    xnpt_nptr->xtmst_originate.xut_seconds  = ntohl(xnpt_nptr->xtmst_originate.xut_seconds );
    xnpt_nptr->xtmst_originate.xut_fraction = ntohl(xnpt_nptr->xtmst_originate.xut_fraction);
    xnpt_nptr->xtmst_receive  .xut_seconds  = ntohl(xnpt_nptr->xtmst_receive  .xut_seconds );
    xnpt_nptr->xtmst_receive  .xut_fraction = ntohl(xnpt_nptr->xtmst_receive  .xut_fraction);
    xnpt_nptr->xtmst_transmit .xut_seconds  = ntohl(xnpt_nptr->xtmst_transmit .xut_seconds );
    xnpt_nptr->xtmst_transmit .xut_fraction = ntohl(xnpt_nptr->xtmst_transmit .xut_fraction);
}

/**
 * @brief 将 x_ntp_packet_t 中的 主机字节序 字段转换为 网络字节序。
 */
static x_void_t ntp_hton_packet(x_ntp_packet_t * xnpt_nptr)
{
#if 0
    xnpt_nptr->xct_li_ver_mode = xnpt_nptr->xct_li_ver_mode;
    xnpt_nptr->xct_stratum     = xnpt_nptr->xct_stratum    ;
    xnpt_nptr->xct_poll        = xnpt_nptr->xct_poll       ;
    xnpt_nptr->xct_percision   = xnpt_nptr->xct_percision  ;
#endif
    xnpt_nptr->xut_root_delay               = htonl(xnpt_nptr->xut_root_delay              );
    xnpt_nptr->xut_root_dispersion          = htonl(xnpt_nptr->xut_root_dispersion         );
    xnpt_nptr->xut_ref_indentifier          = htonl(xnpt_nptr->xut_ref_indentifier         );
    xnpt_nptr->xtmst_reference.xut_seconds  = htonl(xnpt_nptr->xtmst_reference.xut_seconds );
    xnpt_nptr->xtmst_reference.xut_fraction = htonl(xnpt_nptr->xtmst_reference.xut_fraction);
    xnpt_nptr->xtmst_originate.xut_seconds  = htonl(xnpt_nptr->xtmst_originate.xut_seconds );
    xnpt_nptr->xtmst_originate.xut_fraction = htonl(xnpt_nptr->xtmst_originate.xut_fraction);
    xnpt_nptr->xtmst_receive  .xut_seconds  = htonl(xnpt_nptr->xtmst_receive  .xut_seconds );
    xnpt_nptr->xtmst_receive  .xut_fraction = htonl(xnpt_nptr->xtmst_receive  .xut_fraction);
    xnpt_nptr->xtmst_transmit .xut_seconds  = htonl(xnpt_nptr->xtmst_transmit .xut_seconds );
    xnpt_nptr->xtmst_transmit .xut_fraction = htonl(xnpt_nptr->xtmst_transmit .xut_fraction);
}

uint8_t  x_rec_buffer[NTP_PROTOCOL_LEN];
uint8_t rec_flag = 0;
uint8_t ntp_rec_cnt = 0;

x_int64_t xit_tmlst[4] = { 0 };
x_int32_t ntp_get_time_values(x_uint64_t *time_out)
{
    x_int32_t xit_err = -1;
    x_ntp_packet_t  x_send_buffer,tmp_rec_buf;
    x_ntp_timeval_t xtm_value;
    x_int64_t check_time;

    if(rec_flag == 0)
    {
		ntp_init_request_packet(&x_send_buffer);
		ntp_gettimeofday(&xtm_value);
		ntp_timeval_to_timestamp(&x_send_buffer.xtmst_originate, &xtm_value);

		xit_tmlst[0] = (x_int64_t)ntp_timestamp_ns100(&x_send_buffer.xtmst_originate);// T1
		ntp_hton_packet(&x_send_buffer);//T1转成网络字节序

		// 投递请求
		memset(x_rec_buffer,0,NTP_PROTOCOL_LEN);
		ntp_rec_cnt = 0;
		xit_err = uart_write(0,(uint8_t*)&x_send_buffer,sizeof(x_ntp_packet_t));
		if (xit_err < 0)
		{
			return xit_err;
		}
		xit_err = -1;//标志位初始化
		rec_flag = 1;//接受NTP返回帧状态
    }
    else
    {
		//等待接收完成,用接收到的和发送的对比,对比成功继续下面流程,否则重发,重发暂设5次
		if(rec_flag == 2){
			// 转成主机字节序
			ntp_ntoh_packet(&x_rec_buffer);

			memcpy((uint8_t*)&tmp_rec_buf,x_rec_buffer,NTP_PROTOCOL_LEN);
			check_time = (x_int64_t)ntp_timestamp_ns100(&tmp_rec_buf.xtmst_originate ); // T2
			//判断接收数据的正确性
			if(check_time - xit_tmlst[0] < 0.01){
				// T4
				xit_tmlst[3] = (x_int64_t)ntp_gettimevalue();

				xit_tmlst[1] = (x_int64_t)ntp_timestamp_ns100(&tmp_rec_buf.xtmst_receive ); // T2
				xit_tmlst[2] = (x_int64_t)ntp_timestamp_ns100(&tmp_rec_buf.xtmst_transmit); // T3

				*time_out = xit_tmlst[3] + ((xit_tmlst[1] - xit_tmlst[0]) + (xit_tmlst[2] - xit_tmlst[3])) / 2;
				rec_flag = 0;
			}
		}

    }

    if(rec_flag == 0){
    	xit_err = 0;//对时成功
    }

    return xit_err;
}

设计思路

1、首先NTP客户端发送校时帧,按照NTP协议组包;
2、获取当前客户端起始时间戳,将其赋值到发送的校时帧的Originate Timestamp里面;
3、得到T1时间;
4、将客户端发送校时帧转换为网络字节序;
5、发送客户端发送校时帧;
6、等待NTP服务器应答报文,接收该报文并转成主机字节序;
7、得到T2、T3、T4时间;
8、计算offset
9、客户端同步到 服务器的时间戳 T = T4 + ((T2 - T1) + (T3 - T4)) / 2;
10、时间戳 + 定时器时间,即当前客户端时间同步以后的时间;
11、可将同步后的时间,转成北京时间或者UNIX时间戳等方式进行应用;

知识点: 网络字节序 和 主机字节序
参看:UNIX再学习 – 网络IPC:套接字
参看:C语言再学习-- 大端小端详解(转)

所谓网络字节序,其实就是大端字节序(big-endian);主机字节顺序,一般多为小端(little-endian),也就是说,发数据时,先从主机字节序转成网络字节序,然后发送;收数据时,先从网络字节序转成主机字节序,然后使用。
在这里插入图片描述

8、Ubuntu 安装并使用 NTP 服务

  • 首先运行下面的命令 sudo apt-get update 来对系统进行更新。
  • 可以运行命令 sudo apt-get install ntp 来安装 NTP 客户端
  • 使用 sntp --version 查看ntp版本号。
    在这里插入图片描述
  • 配置 NTP Pool
    Pool 的配置文件位于 /etc/ntp.conf 文件中。将Ubuntu的注释掉,新增自己的服务器IP pool 服务器IP
    在这里插入图片描述
  • 启动服务指令为 sudo systemctl restart ntp
  • 查看服务状态指令为 sudo systemctl status ntp

9、国内可用的Internet时间同步服务器地址(NTP时间服务器)

国内可用的Internet时间同步服务器地址(NTP时间服务器) 好在阿里云提供了7个NTP时间服务器也就是Internet时间同步服务器地址
ntp1.aliyun.com (IP为120.25.115.20 端口为123)
ntp2.aliyun.com
ntp3.aliyun.com
ntp4.aliyun.com
ntp5.aliyun.com
ntp6.aliyun.com
ntp7.aliyun.com
以下是能ping 通的地址,不能平通的也许别人能ping通所以也粘出来了!
210.72.145.44 (国家授时中心服务器IP地址)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聚优致成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值