娄底本地做寄生虫网站,中国互联网站建设中心,品牌推广策划营销策划,信誉好的网站建设公司文章目录 1 NTP1.1 简介1.2 包结构1.3 UNIX 时间戳和NTP时间戳 2 代码实现2.1 实现步骤2.2 完整代码 3 结果 在某些场景下#xff0c;单片机需要通过网络获取准确的时间进行数据同步#xff0c;例如日志记录、定时任务等。然而#xff0c;单片机本身无法直接获得准确的标准时… 文章目录 1 NTP1.1 简介1.2 包结构1.3 UNIX 时间戳和NTP时间戳 2 代码实现2.1 实现步骤2.2 完整代码 3 结果 在某些场景下单片机需要通过网络获取准确的时间进行数据同步例如日志记录、定时任务等。然而单片机本身无法直接获得准确的标准时间往往需要依靠网络时间协议(NTP)服务器来同步时间。本文将详细介绍如何通过NTP服务器获取准确的网络时间。
1 NTP
1.1 简介
NTP(Network Time Protocol网络时间协议是一种用于使计算机时钟同步到互联网标准时间的协议。NTP服务器通常分层级(Stratum)运作一级服务器直接与时间基准同步而其他级别的服务器则从更高级别的服务器获取时间。通过NTP我们可以让系统的时钟保持与标准时间的一致性。
1.2 包结构
NTP包通常由以下字段组成
li_vn_mode润秒指示器2位、版本号3位和模式3位。stratum服务器层级1表示主服务器2及更高层级表示从服务器0表示未同步。poll连续NTP请求间的最大间隔以2的幂次表示。precision服务器时钟的精度以2的幂次表示。root_delay从NTP客户端到NTP服务器的根延迟秒。root_dispersionNTP服务器与其上一级时钟源的偏差秒。reference_identifierNTP服务器的参考标识符通常指示服务器的源。reference_timestamp参考时间戳。origin_timestamp发起时间戳。receive_timestamp接收时间戳。transmit_timestamp传输时间戳。
1.3 UNIX 时间戳和NTP时间戳
UNIX时间戳指从1970年1月1日00:00:00 UTC开始到当前时刻的秒数。NTP时间戳指从1900年1月1日00:00:00 UTC开始到当前时刻的秒数。
偏移量的计算
因为 NTP 时间戳的起点比 UNIX 时间戳的起点早 70 年从1900年到1970年。假设这 70 年内没有闰秒70 年一共 25567 天每天86400秒。因此这个时间差的秒数为 70 * 365 17天17天是因为这段时间内包括了17 次闰年每天86400秒。所以这个偏移量可以计算为2208988800秒即70 * 365 * 86400 17 * 86400。
偏移量的用途
在NTP协议中时间戳以从1900年开始的秒数表示而在许多其他系统中(如Unix系统时间以从1970年开始的秒数表示。因此需要将NTP时间戳转换为UNIX时间戳或者将UNIX时间戳转换为NTP时间戳则需要用到上面的偏移。
2 代码实现
2.1 实现步骤
这里在Linux环境下为例对NTP时间进行获取使用标准的POSIX/BSD套接字编程这样如果想在单片机中LwIP实现的话也可以直接使用。
1、NTP服务端地址
这里我们以阿里云的NTP服务器ntp3.aliyun.com为例来获取获取网络时间它的端口为123。
#define NTP_SERVER 203.107.6.88 // NTP服务器地址:ntp3.aliyun.com
#define NTP_PORT 123 // NTP服务器端口号本示例中不做DNS解析我这里ping阿里的ntp服务器地址ntp3.aliyun.com的ip为203.107.6.88。若用在程序中建议做DNS解析。
2、NTP结构体
// NTP数据包结构体
typedef struct {uint8_t li_vn_mode; // 润秒指示器(2),版本号(3),模式(3)uint8_t stratum; // 服务器层级,1-主服务器,2-从服务器,0-未同步uint8_t poll; // 连续NTP请求间的最大间隔,2的幂表示uint8_t precision; // 服务器时钟的精度,2的幂表示(秒)uint32_t root_delay; // 从NTP客户端到NTP服务器的根延迟(秒)uint32_t root_dispersion; // NTP服务器与其上一级时钟源的偏差(秒)uint32_t reference_identifier; // NTP服务器的参考标识符,通常指示了服务器的源uint32_t reference_timestamp[2]; // 参考时间戳uint32_t origin_timestamp[2]; // 发起时间戳uint32_t receive_timestamp[2]; // 接收时间戳uint3232 transmit_timestamp[2]; // 传输时间戳
} ntp_packet;2、创建套接字并设置超时时间
int sockfd;
struct timeval timeout;timeout.tv_sec NTP_RCV_TIMEO / 1000;
timeout.tv_usec (NTP_RCV_TIMEO % 1000) * 1000;// 创建socket
sockfd socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, timeout, sizeof(timeout));这里创建套接字后需要使用setsockopt设置套接字的接收超时时间。这是因为NTP是UDP协议的有可能请求了NTP报文后请求报文没发送出去或响应报文没被正确接收所以过了超时时间还没有接收到NTP时间的话我们不能卡在读函数中。
3、设置服务端地址和NTP请求报文
#define NTP_UNIX_OFFSET 2208988800UL // UNIX时间戳(1970.1.1)和NTP时间戳(1900.1.1)之间的偏移量
struct sockaddr_in server_addr;
// 设置服务器地址
server_addr.sin_family AF_INET;
server_addr.sin_port htons(NTP_PORT);
server_addr.sin_addr.s_addr inet_addr(NTP_SERVER);// 设置NTP版本和客户端模式
packet.li_vn_mode (0x3 6) | (0x3 3) | 0x3; // NTP版本3、客户端模式
packet.transmit_timestamp[0] htonl(NTP_UNIX_OFFSET); // 设置传输时间为偏移,则后面无需转换在网络协议中大多数字段都遵循“大端字节序”即高位字节在前低位字节在后。htonl 将主机字节序的 NTP_UNIX_OFFSET 转换为符合 NTP 协议要求的字节序。
4、请求、获取并输出NTP时间
// 请求NTP时间
sendto(sockfd, (const char*)packet, sizeof(packet), 0, (struct sockaddr*)server_addr, sizeof(server_addr));
recv_len recvfrom(sockfd, (char*)packet, sizeof(packet), 0, NULL, NULL);
// 成功接收计算NTP时间
ntp_time (time_t)(ntohl(packet.transmit_timestamp[0]) - NTP_UNIX_OFFSET);
printf(NTP time: %s, ctime(ntp_time));
close(sockfd);2.2 完整代码
#include stdio.h
#include stdlib.h
#include string.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include time.h
#include unistd.h#define NTP_SERVER 203.107.6.88 // NTP服务器地址:ntp3.aliyun.com
#define NTP_PORT 123 // NTP服务器端口号
#define NTP_PACKET_SIZE 48 // NTP数据包大小
#define NTP_UNIX_OFFSET 2208988800UL // UNIX时间戳(1970.1.1)和NTP时间戳(1900.1.1)之间的偏移量
#define NTP_RCV_TIMEOUT 2 // 接收超时时间秒
#define MAX_RETRIES 5 // 最大重试次数// NTP数据包结构体
typedef struct {uint8_t li_vn_mode; // 润秒指示器(2),版本号(3),模式(3)uint8_t stratum; // 服务器层级,1-主服务器,2-从服务器,0-未同步uint8_t poll; // 连续NTP请求间的最大间隔,2的幂表示uint8_t precision; // 服务器时钟的精度,2的幂表示(秒)uint32_t root_delay; // 从NTP客户端到NTP服务器的根延迟(秒)uint32_t root_dispersion; // NTP服务器与其上一级时钟源的偏差(秒)uint32_t reference_identifier; // NTP服务器的参考标识符,通常指示了服务器的源uint32_t reference_timestamp[2]; // 参考时间戳uint32_t origin_timestamp[2]; // 发起时间戳uint32_t receive_timestamp[2]; // 接收时间戳uint32_t transmit_timestamp[2]; // 传输时间戳
} ntp_packet;void ntp_get_time_once() {int sockfd;struct sockaddr_in server_addr;ntp_packet packet {0};time_t ntp_time;int retries 0;int recv_len;struct timeval timeout;timeout.tv_sec NTP_RCV_TIMEOUT;timeout.tv_usec 0;// 创建socketsockfd socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sockfd 0) {perror(socket failed);return;}// 设置接收超时时间setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, timeout, sizeof(timeout));// 设置NTP服务器地址memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(NTP_PORT);server_addr.sin_addr.s_addr inet_addr(NTP_SERVER);// 初始化NTP请求数据包packet.li_vn_mode (0x3 6) | (0x3 3) | 0x3; // NTP版本3、客户端模式packet.transmit_timestamp[0] htonl(NTP_UNIX_OFFSET);// 重试逻辑while (retries MAX_RETRIES) {// 发送NTP请求if (sendto(sockfd, (const char*)packet, sizeof(packet), 0, (struct sockaddr*)server_addr, sizeof(server_addr)) 0) {perror(sendto failed);close(sockfd);return;}// 接收NTP响应recv_len recvfrom(sockfd, (char*)packet, sizeof(packet), 0, NULL, NULL);if (recv_len 0) {// 成功接收计算NTP时间ntp_time (time_t)(ntohl(packet.transmit_timestamp[0]) - NTP_UNIX_OFFSET);printf(NTP time: %s, ctime(ntp_time));close(sockfd);return;} else {perror(recvfrom failed, retrying...);retries;}}// 达到最大重试次数后失败printf(NTP sync failed after %d retries.\n, MAX_RETRIES);close(sockfd);
}int main() {ntp_get_time_once();return 0;
}我这里代码就是简单地重新请求固定次数后就退出。大家可以根据实际情况进行更改比如一定要获取到了再返回。
3 结果
编译上面的代码并运行可以看到输出了当前的时间 这里阿里的NTP服务器返回的是标准的UTC时间加上12小时为当前的北京时间。