基于c/c++实现linux/windows跨平台获取ntp网络时间戳

目录

使用场景

在项目中,有时需要根据时钟同步做一些操作,例如网络请求、多设备视频同步播放等。

在多设备同步播放视频的环境中,我们可以定时请求网络时间,并根据每次请求的耗时取均值做一下滤波,这样就能使得我们的各设备同步播放视频更加准确。

c/c++源码

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

cpp 复制代码
#include <cstdint>
#include <cstring>
#include <string>
#include <iostream>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#endif


// NTP 客户端返回码
#define NTP_OK             0
#define NTP_ERR_WSA_INIT   -1
#define NTP_ERR_SOCKET     -2
#define NTP_ERR_INVALID_IP -3
#define NTP_ERR_SENDTO     -4
#define NTP_ERR_RECV       -5
#define NTP_ERR_DNS        -6

/**
 * @brief 设置请求超时
 * @param sock 套接字fd
 * @param timeout_ms 超时时间(单位:毫秒)
 */
static void SetSocketTimeout(int sock, int timeout_ms) {
#ifdef _WIN32
    DWORD timeout = (DWORD)timeout_ms;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout));
#else
    struct timeval tv {};
    tv.tv_sec = timeout_ms / 1000;
    tv.tv_usec = (timeout_ms % 1000) * 1000;
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
#endif
}

/**
* @brief 关闭套接字
* @param sock 套接字fd
*/
static void CloseSocket(int sock) {
#ifdef _WIN32
    closesocket(sock);
#else
    close(sock);
#endif
}

// NTP 报文结构体 (SNTPv4 RFC 4330)
struct NtpPacket {
    uint8_t li_vn_mode = 0x1B;      // 00 110 011: 无警告、NTPv4、客户端模式
    uint8_t stratum = 0;            // 服务器层级
    uint8_t poll = 0;               // 轮询间隔
    uint8_t precision = 0;          // 时钟精度

    uint32_t root_delay = 0;        // 根延迟
    uint32_t root_dispersion = 0;   // 误差范围
    uint32_t ref_id = 0;            // 参考ID

    uint32_t ref_ts_sec = 0;        // 参考时间戳(秒)
    uint32_t ref_ts_frac = 0;       // 参考时间戳(小数秒)

    uint32_t orig_ts_sec = 0;       // 客户端发起请求时间(秒)
    uint32_t orig_ts_frac = 0;      // 客户端发起请求时间(小数秒)

    uint32_t recv_ts_sec = 0;       // 服务器接收请求时间(秒)
    uint32_t recv_ts_frac = 0;      // 服务器接收请求时间(小数秒)

    uint32_t trans_ts_sec = 0;      // 服务器回复时间(秒)【核心字段】
    uint32_t trans_ts_frac = 0;     // 服务器回复时间(小数秒)
};

/**
* @brief 获取 NTP 时间戳(毫秒级)
* @param ntp_time_ms 出参毫秒级ntp时间
* @param server ntp服务器地址
* @param timeout_ms 请求超时时间(单位:毫秒)
* @return NTP_OK 获取成功;其他 获取失败
*/
static int GetNtpTimestamp(int64_t& ntp_time_ms, const std::string& server = "ntp.aliyun.com", int timeout_ms = 2000)
{
    ntp_time_ms = 0;

#ifdef _WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        return NTP_ERR_WSA_INIT;
    }
#endif

    int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock < 0) {
#ifdef _WIN32
        WSACleanup();
#endif
        return NTP_ERR_SOCKET;
    }

    SetSocketTimeout(sock, timeout_ms);

    // 域名解析
    struct addrinfo hints {};
    hints.ai_family = AF_INET;       // IPv4 only
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    struct addrinfo* result = nullptr;
    int ret = getaddrinfo(server.c_str(), "123", &hints, &result);
    if (ret != 0 || result == nullptr) {
        CloseSocket(sock);
#ifdef _WIN32
        WSACleanup();
#endif
        return NTP_ERR_DNS;
    }

    // 直接使用解析后的地址
    struct sockaddr_in addr {};
    memcpy(&addr, result->ai_addr, result->ai_addrlen);
    freeaddrinfo(result);  // 必须释放

    // NTP 报文 (SNTPv4)
    struct NtpPacket pkt{};

    if (sendto(sock, (const char*)&pkt, sizeof(pkt), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        CloseSocket(sock);
#ifdef _WIN32
        WSACleanup();
#endif
        return NTP_ERR_SENDTO;
    }

    socklen_t addr_len = sizeof(addr);
    if (recvfrom(sock, (char*)&pkt, sizeof(pkt), 0, (struct sockaddr*)&addr, &addr_len) < 0) {
        CloseSocket(sock);
#ifdef _WIN32
        WSACleanup();
#endif
        return NTP_ERR_RECV;
    }

    // ========================
    // 关键:秒 + 小数 → 毫秒级时间戳
    // ========================
    uint64_t sec = ntohl(pkt.trans_ts_sec);
    uint64_t frac = ntohl(pkt.trans_ts_frac);

    const int64_t NTP_OFFSET = 2208988800LL;
    int64_t unix_sec = (int64_t)sec - NTP_OFFSET;
    int64_t ms = (int64_t)((double)frac * 1000.0 / 4294967296.0);

    ntp_time_ms = unix_sec * 1000LL + ms;

    CloseSocket(sock);
#ifdef _WIN32
    WSACleanup();
#endif

    return NTP_OK;
}

int main(int argc, char* argv[])
{
    std::string ntp_servive{ "ntp.aliyun.com" };
    if (2 == argc) {
        ntp_servive = argv[1];
    }
    std::cout << "server address:" << ntp_servive << std::endl;

    int64_t time_now = 0;
    int ret = GetNtpTimestamp(time_now, ntp_servive, 2000);

    if (ret == NTP_OK) {
        std::cout << "Get Unix timestamp:" << time_now << std::endl;
    } else {
        std::cout << "Failed to get timestamp,error code:" << ret << std::endl;
    }

    return 0;
}

结果验证

windows编译命令

powershell 复制代码
g++ -std=c++11 ntp_client.cpp -o ntp_time -lws2_32

linux编译命令

powershell 复制代码
g++ -std=c++11 ntp_client.cpp -o ntp_time

输出结果

bash 复制代码
$ ./ntp_client.exe
Get Unix timestamp:1776244593733
相关推荐
程序员与背包客_CoderZ1 小时前
高性能分布式KV存储引擎RocksDB入门与C/C++编码实战
c语言·开发语言·数据库·c++·分布式·分布式数据库·rocksdb
进击的_鹏1 小时前
从零开始的 Redis 学习
服务器·数据库·c++·redis·缓存
william_yangshun1 小时前
【大模型部署实战】kimi-k3-in-c 中文版:用 176KB 纯 C99 引擎在 CPU 上跑 Kimi K3 上手指南
c语言·开发语言
实心儿儿1 小时前
Linux —— 传输层协议TCP
linux·服务器·tcp/ip
祖力551 小时前
Linux应用软件编程:目录IO与framebuffer
linux·运维·算法·framebuffer·目录io
词却2 小时前
Linux基础:常用基本命令与软件包管理
linux
_Narcissus_2 小时前
链表算法题和静态链表
数据结构·c++·笔记·算法·链表·ai·力扣
布莱克6052 小时前
C++拷贝构造与拷贝赋值运算符的区别详解
开发语言·c++
Flag在我手里3 小时前
应急响应-Windows权限维持
windows·rdp·应急响应·玄机
2401_890603403 小时前
Linux 进程控制
linux·运维·服务器