基于c/c++实现linux/windows跨平台ntp时间戳服务器

目录

使用场景

在某些严格要求时间同步很精准的项目中,获取网络ntp时间的时间延时比较大,做滤波处理可能效果也不理想。因此可以搭建一个本地ntp服务器,这样可以大大缩短网络链路,使得ntp时间更加精准。

c/c++源码

废话不多说,我们直接上源码,新建文件并命名为ntp_server.cpp,然后将下方源码拷贝到文件里。

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <stdint.h>

// 跨平台套接字头文件与库
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#define close closesocket
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SOCKET int
#define INVALID_SOCKET -1
#endif

// NTP 版本3 数据包结构 (RFC 1305)
#pragma pack(push, 1)
struct NtpPacket {
    // LI(2位) + Version(3位) + Mode(3位)
    uint8_t li_vn_mode;
    uint8_t stratum;         // 层级
    uint8_t poll;            // 轮询间隔
    uint8_t precision;       // 精度
    uint32_t rootDelay;      // 根延迟
    uint32_t rootDispersion; // 根离散度
    uint32_t refId;          // 参考ID

    uint32_t refTimestampSec;   // 参考时间戳(秒)
    uint32_t refTimestampFrac;  // 参考时间戳(小数)

    uint32_t origTimestampSec;  // 原始时间戳(秒)
    uint32_t origTimestampFrac; // 原始时间戳(小数)

    uint32_t recvTimestampSec;  // 接收时间戳(秒)
    uint32_t recvTimestampFrac; // 接收时间戳(小数)

    uint32_t transTimestampSec; // 发送时间戳(秒)
    uint32_t transTimestampFrac;// 发送时间戳(小数)
};
#pragma pack(pop)

// NTP 时间基准: 1900-01-01 到 1970-01-01 的秒数
const uint64_t NTP_TIMESTAMP_DELTA = 2208988800ULL;

// 跨平台初始化网络库
static bool InitSocket() {
#ifdef _WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Winsock initialization failed" << std::endl;
        return false;
    }
#endif
    return true;
}

// 跨平台清理网络库
static void CleanupSocket() {
#ifdef _WIN32
    WSACleanup();
#endif
}

// 获取当前系统时间转换为 NTP 时间戳
static void GetNtpTime(uint32_t& sec, uint32_t& frac) {
    // 获取 Unix 时间 (1970-01-01 起的秒)
    time_t unixTime = time(nullptr);
    uint64_t ntpSec = (uint64_t)unixTime + NTP_TIMESTAMP_DELTA;

    sec = htonl((uint32_t)ntpSec);
    // 小数部分: 简单填充 0 (生产环境可使用高精度时钟)
    frac = htonl(0);
}

int main() {
    if (!InitSocket()) {
        return -1;
    }

    // 创建 UDP 套接字
    SOCKET serverFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (serverFd == INVALID_SOCKET) {
        std::cerr << "Failed to create socket" << std::endl;
        CleanupSocket();
        return -1;
    }

    // 绑定 0.0.0.0:123 (NTP 默认端口)
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(123);

    if (bind(serverFd, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        std::cerr << "Failed to bind port 123! (Administrator/root privileges required)" << std::endl;
        close(serverFd);
        CleanupSocket();
        return -1;
    }

    std::cout << "NTP server started successfully, listening on 0.0.0.0:123" << std::endl;
    std::cout << "Waiting for client requests..." << std::endl;

    // 循环接收请求并应答
    sockaddr_in clientAddr{};
    socklen_t clientLen = sizeof(clientAddr);
    NtpPacket packet{};

    while (true) {
        memset(&packet, 0, sizeof(packet));

        // 接收 NTP 请求
        ssize_t recvLen = recvfrom(
            serverFd,
            (char*)&packet,
            sizeof(packet),
            0,
            (sockaddr*)&clientAddr,
            &clientLen
        );

        if (recvLen < 0) continue;

        // 打印客户端信息
        char clientIp[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &clientAddr.sin_addr, clientIp, INET_ADDRSTRLEN);
        std::cout << "Received NTP request from " << clientIp << std::endl;

        // 构造 NTP 应答
        // LI=0, VN=3 (NTPv3), Mode=4 (Server)
        packet.li_vn_mode = (0 << 6) | (3 << 3) | 4;
        packet.stratum = 1;   // 层级 1 (参考本地时钟)
        packet.poll = 6;
        packet.precision = 0xFA;

        // 填充时间戳
        GetNtpTime(packet.refTimestampSec, packet.refTimestampFrac);
        GetNtpTime(packet.recvTimestampSec, packet.recvTimestampFrac);
        GetNtpTime(packet.transTimestampSec, packet.transTimestampFrac);

        // 发送应答
        sendto(
            serverFd,
            (const char*)&packet,
            sizeof(packet),
            0,
            (sockaddr*)&clientAddr,
            clientLen
        );
    }

    // 理论上不会执行到这里
    close(serverFd);
    CleanupSocket();
    return 0;
}

结果验证

windows编译命令

shell 复制代码
g++ -std=c++11 ntp_server.cpp -o ntp_server -lws2_32

linux编译命令

shell 复制代码
g++ -std=c++11 ntp_server.cpp -o ntp_server

服务器输出结果

shell 复制代码
.\ntp_server.exe
NTP server started successfully, listening on 0.0.0.0:123
Waiting for client requests...
Received NTP request from 192.168.0.149

客户端输出结果

客户端实现可参考基于c/c++实现linux/windows跨平台获取ntp网络时间戳

shell 复制代码
.\ntp_client.exe 192.168.0.149
server address:192.168.0.149
Get Unix timestamp:1776611741000
相关推荐
炸膛坦客32 分钟前
单片机/C/C++八股:(二十四)编译文件( .bin 和 .hex ,包括 .elf 和 .axf )
c语言·c++·单片机
疯狂成瘾者1 小时前
Java 常见集合方法
java·windows·python
mounter6251 小时前
走向长时运行:引入协程(Coroutines),打破 BPF 程序的“一堂到底”限制
linux·ebpf·kernel
cpp_25011 小时前
P10098 [ROIR 2023] 地铁建设 (Day 2)
数据结构·c++·算法·贪心·二分答案·洛谷题解
倒流时光三十年2 小时前
MyBatis @MapKey 注解详解
windows·mybatis
生活、追梦者2 小时前
【Windows】 CMD 黑窗口命令大全
windows·命令行·cmd·运维技巧
六点_dn2 小时前
Linux学习笔记-shell运算符
linux·笔记·学习
hehelm2 小时前
IO 多路复用 — Reactor
linux·服务器·网络·数据库·c++
浅止菌3 小时前
嵌入式项目Makefile越写越乱?一张万能模板+5个技巧,告别重复劳动
linux
简单电磁炮3 小时前
硬件基础4
c语言