基于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
相关推荐
ltl1 小时前
大多数「无锁」代码其实不是无锁的
linux
ltl1 小时前
Linux 内核的内存屏障:一个让我调了三天的 bug
linux
Lonely 净土4 小时前
Rocky Linux 安装教程
linux·运维·服务器
啊啊啊啊啊!!!!5 小时前
【c++】stack和queue的接口使用以及底层实现
开发语言·c++
上海安当技术7 小时前
C/S 桌面软件怎么接 UKey 强认证?C# SDK 与 C 动态库集成 WinForms/Qt/工控实战
c语言·qt·c#
李白你好8 小时前
Windows 离线 Linux 提权辅助查询工具
linux
(Charon)8 小时前
【C++】锁与原子操作(四):自旋锁的完整实现与性能优化
c语言·开发语言·c++
Lust Dusk8 小时前
记一次Linux应急响应题目解析
linux·运维·服务器·网络·安全·网络安全
ShineWinsu9 小时前
对于Linux:五种IO模型以及非阻塞IO的详细解析
linux·c++·面试·io·阻塞·非阻塞·fcntl
千千寰宇9 小时前
[Linux/Ollama] Termux : 一款运行在 Android 平台上的开源终端模拟器与 Linux 环境应用、支持在手机上基于Ollama部署LLM
linux