基于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
相关推荐
funnycoffee12331 分钟前
Cisco Firewpower 4100 9300 FXOS change management ip address
linux·数据库·tcp/ip
Han_han91934 分钟前
List系列集合:
数据结构·windows·list
ZPC821041 分钟前
规划后的轨迹,如何发给 moveit_servo 执行
c++·人工智能·算法·3d
杜子不疼.42 分钟前
【C++ 在线五子棋对战】 - 工具类模块实现
开发语言·c++
呉師傅44 分钟前
统信UOS如何安装本地打印机驱动以及URL查找网络打印机并安装驱动方法
运维·服务器·网络·windows·电脑
青梅橘子皮1 小时前
Linux---开发工具(1)(vim,gcc/g++)
linux·运维·服务器
邮专薛之谦1 小时前
Linux常用指令大全(完整版)
linux·运维·服务器
永远自我1 小时前
matlab对c语言模块进行仿真
c语言·开发语言
jfqqqqq1 小时前
记一次ubuntu 22.04安装旧版 MongoDB 4.2
linux·mongodb·ubuntu
BING_Algorithm1 小时前
开发常用Linux命令
linux·后端