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

如何利用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版本号
Mode3模式(客户端、服务器等)
Stratum8层级
Poll8轮询间隔
Precision8精度
Root Delay32到根参考源的总往返延迟
Root Dispersion32到根参考源的最大误差
Reference Identifier32参考源标识符
Reference Timestamp64参考时间戳
Originate Timestamp64请求发送时间戳
Receive Timestamp64请求接收时间戳
Transmit Timestamp64响应发送时间戳

三、使用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;
}