当前位置:首页 > 行业动态 > 正文

如何利用NTP服务器实现时间同步?

NTP服务器通过UDP协议在端口123上发送和接收时间戳,实现网络中设备的时间同步。

C语言NTP服务器同步时间详解

NTP(Network Time Protocol,网络时间协议)是一种用于计算机网络中设备时间同步的协议,NTP通过UDP协议在端口号123上发送和接收时间戳,帮助设备校准时间,确保整个网络中的时间一致性,本文将详细介绍如何在C语言中实现NTP时间同步,包括NTP协议简介、数据包结构、使用UDP套接字进行通信、解析NTP响应数据包以及设置系统时间。

一、NTP协议简介

NTP用于在网络上同步计算机时间,它使用UDP协议,端口号为123,NTP客户端发送请求到NTP服务器,服务器返回包含时间戳的数据包,客户端计算往返延迟和时差,从而调整本地时间,NTP协议实现了高精度时间同步,通常误差在毫秒级别。

二、NTP数据包结构

NTP数据包有固定的48字节结构,包含以下关键字段:

字段名 长度 (比特) 描述
LI (Leap Indicator) 2 告警指示器
VN (Version Number) 3 版本号
Mode 3 模式(客户端、服务器等)
Stratum 8 层级
Poll 8 轮询间隔
Precision 8 精度
Root Delay 32 到根参考源的总往返延迟
Root Dispersion 32 到根参考源的最大误差
Reference Identifier 32 参考源标识符
Reference Timestamp 64 参考时间戳
Originate Timestamp 64 请求发送时间戳
Receive Timestamp 64 请求接收时间戳
Transmit Timestamp 64 响应发送时间戳

三、使用NTP协议同步时间

1. 建立UDP套接字

创建一个UDP套接字,用于与NTP服务器通信,以下是创建UDP套接字的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h>
#define NTP_SERVER "pool.ntp.org"
#define NTP_PORT 123
#define NTP_PACKET_SIZE 48
int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    unsigned char packet[NTP_PACKET_SIZE] = {0};
    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(NTP_PORT);
    if (inet_pton(AF_INET, NTP_SERVER, &server_addr.sin_addr) <= 0) {
        perror("inet_pton error");
        close(sockfd);
        return -1;
    }
    // 设置NTP请求数据包
    packet[0] = 0x1B; // LI = 0, VN = 3, Mode = 3 (client)
    // ...其他字段设置为0...
    if (sendto(sockfd, packet, NTP_PACKET_SIZE, 0, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto error");
        close(sockfd);
        return -1;
    }
    // 接收NTP响应数据包
    socklen_t addr_len = sizeof(server_addr);
    if (recvfrom(sockfd, packet, NTP_PACKET_SIZE, 0, (struct sockaddr*)&server_addr, &addr_len) < 0) {
        perror("recvfrom error");
        close(sockfd);
        return -1;
    }
    close(sockfd);
    return 0;
}

2. 解析NTP响应数据包

接收到NTP响应数据包后,需要解析时间戳字段并将其转换为本地时间,以下是解析NTP响应数据包的示例代码:

#include <stdint.h>
#define NTP_TIMESTAMP_DELTA 2208988800ull
void parse_ntp_packet(unsigned char *packet) {
    uint32_t txTm_s, txTm_f;
    txTm_s = (uint32_t)packet[40] << 24 | (uint32_t)packet[41] << 16 | (uint32_t)packet[42] << 8 | (uint32_t)packet[43];
    txTm_f = (uint32_t)packet[44] << 24 | (uint32_t)packet[45] << 16 | (uint32_t)packet[46] << 8 | (uint32_t)packet[47];
    time_t txTm = (time_t)(txTm_s NTP_TIMESTAMP_DELTA);
    printf("Time: %s", ctime(&txTm));
}

3. 设置系统时间

解析出时间后,可以使用系统API将其设置为本地时间,以下是设置系统时间的示例代码:

#include <sys/time.h>
void set_system_time(time_t txTm) {
    struct timeval tv;
    tv.tv_sec = txTm;
    tv.tv_usec = 0;
    if (settimeofday(&tv, NULL) < 0) {
        perror("settimeofday error");
    }
}

四、完整代码示例

以下是完整的C语言代码示例,演示如何通过NTP协议同步时间:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
#define NTP_SERVER "pool.ntp.org"
#define NTP_PORT 123
#define NTP_PACKET_SIZE 48
#define NTP_TIMESTAMP_DELTA 2208988800ull
int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    unsigned char packet[NTP_PACKET_SIZE] = {0};
    time_t txTm;
    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(NTP_PORT);
    if (inet_pton(AF_INET, NTP_SERVER, &server_addr.sin_addr) <= 0) {
        perror("inet_pton error");
        close(sockfd);
        return -1;
    }
    // 设置NTP请求数据包
    packet[0] = 0x1B; // LI = 0, VN = 3, Mode = 3 (client)
    // ...其他字段设置为0...
    if (sendto(sockfd, packet, NTP_PACKET_SIZE, 0, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("sendto error");
        close(sockfd);
        return -1;
    }
    // 接收NTP响应数据包
    socklen_t addr_len = sizeof(server_addr);
    if (recvfrom(sockfd, packet, NTP_PACKET_SIZE, 0, (struct sockaddr*)&server_addr, &addr_len) < 0) {
        perror("recvfrom error");
        close(sockfd);
        return -1;
    }
    close(sockfd);
    // 解析NTP响应数据包
    uint32_t txTm_s, txTm_f;
    txTm_s = (uint32_t)packet[40] << 24 | (uint32_t)packet[41] << 16 | (uint32_t)packet[42] << 8 | (uint32_t)packet[43];
    txTm_f = (uint32_t)packet[44] << 24 | (uint32_t)packet[45] << 16 | (uint32_t)packet[46] << 8 | (uint32_t)packet[47];
    txTm = (time_t)(txTm_s NTP_TIMESTAMP_DELTA);
    printf("Time: %s", ctime(&txTm));
    // 设置系统时间
    struct timeval tv;
    tv.tv_sec = txTm;
    tv.tv_usec = 0;
    if (settimeofday(&tv, NULL) < 0) {
        perror("settimeofday error");
    } else {
        printf("System time updated successfully.
");
    }
    return 0;
}
0