基于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
相关推荐
智者知已应修善业几秒前
【51单片机用T0定时器方式1,实现0.5S的时间间隔实现第一次一个灯亮、第二次二个灯亮,直到全部灯亮,然后重复整个过程】2023-12-29
c++·经验分享·笔记·算法·51单片机
一起逃去看海吧16 分钟前
dify-03
java·linux·开发语言
fengyehongWorld17 分钟前
Linux 根据端口进行的相关查询
linux
lihao lihao20 分钟前
linux匿名管道
linux·运维·服务器
うちは止水23 分钟前
weston出图调试
linux·wayland·weston
STDD24 分钟前
Farming Simulator 25(模拟农场 25) Linux 专服搭建完全指南
linux·运维·javascript
社交怪人26 分钟前
【范围判断】信息学奥赛一本通C语言解法(题号2052)
c语言
智者知已应修善业32 分钟前
【51单片机4位静态数码管显示1234】2023-11-14
c++·经验分享·笔记·算法·51单片机
抓虾爪35 分钟前
ST意法代理商粤科源兴丨LSM6DS3全系列现货库存,LSM6DS3TR-C当天可发
c++