基于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
相关推荐
ulias2122 小时前
进程初识(1)
linux·运维·服务器·网络·c++
t***5442 小时前
Orwell Dev-C++ 和 Embarcadero Dev-C++ 哪个更好
开发语言·c++
发发就是发2 小时前
TTY子系统与线路规程:那个让我深夜抓狂的串口“丢包”问题
linux·服务器·驱动开发·单片机·嵌入式硬件
Shingmc32 小时前
【Linux】网络基础概念
linux·服务器·网络
舒一笑2 小时前
Windows 下执行 pnpm install 报 EBUSY: resource busy or locked,我最后用这一招解决了
前端·windows·程序员
BestOrNothing_20152 小时前
C++零基础到工程实战(4.3.4):vector数组搜索、删除、插入与排序
c++·vector·sort·find·insert·动态数组·erase
Paraverse_徐志斌3 小时前
Linux 内核与 Zero-Copy 零拷贝
linux·运维·内核·零拷贝
Fuyo_11193 小时前
带你了解C++的类与对象(中)
c++
小苗卷不动3 小时前
UDP服务端收发流程
linux·c++·udp