QT5 ntp获取时间

#include "ntp_network.h"

#include <QDateTime>
#include <QUdpSocket>
#include <QDebug>
#include <QtEndian>
#include <QElapsedTimer>
#include <QMetaEnum>

#ifdef Q_OS_WIN
#include <Windows.h>
#endif
#ifdef Q_OS_LINUX
#include <sys/time.h>
#endif

ntp_network::ntp_network(QObject *parent) : QObject(parent)
{
    m_socket = new QUdpSocket(this);
    connect(m_socket, &QUdpSocket::connected, this, &ntp_network::on_connected);
    connect(m_socket, &QUdpSocket::readyRead, this, &ntp_network::on_readData);
}

/**
 * @brief        连接Ntp服务器,端口号默认123
 * @param url    Ntp服务器IP地址或网址
 */
void ntp_network::connectServer(QString url)
{
    close();
    m_socket->connectToHost(url, 123);
}

void ntp_network::close()
{
    m_socket->abort();
}

void ntp_network::on_connected()
{
    qDebug() << "连接成功!";
    QMetaEnum m = QMetaEnum::fromType<QAbstractSocket::SocketState>();      // 获取QUdpSocket连接状态字符串
    emit updateData(QString("连接成功:%1  %2").arg(m_socket->peerName()).arg(m.key(m_socket->state())));
    getTime();
}

void ntp_network::getTime()
{
    sendData();
}

QByteArray toNtpPacket() {
    QByteArray result(40, 0);

    quint8 li = 0;                   // LI闰秒标识器,占用2个bit,0 即可;
    quint8 vn = 3;                   // VN 版本号,占用3个bits,表示NTP的版本号,现在为3;
    quint8 mode = 3;                 // Mode 模式,占用3个bits,表示模式。 3 表示 client, 2 表示 server
    quint8 stratum = 0;              // 系统时钟的层数,取值范围为1~16,它定义了时钟的准确度。层数为1的时钟准确度最高,准确度从1到16依次递减,层数为16的时钟处于未同步状态,不能作为参考时钟。
    quint8 poll = 4;                 // 轮询时间,即两个连续NTP报文之间的时间间隔(4-14)
    qint8 precision = -6;            // 系统时钟的精度,精确到秒的平方级(-6 到 -20)

    result[0] = char((li << 6) | (vn <<3) | (mode));
    result[1] = char(stratum & 0xff);

    result[2] = char(poll & 0xff);
    result[3] = char(precision & 0xff);

    qint64 currentLocalTimestamp = QDateTime::currentMSecsSinceEpoch();
    result.append((const char *)&currentLocalTimestamp, sizeof(qint64));

    return result;
}

/**
 * @brief 发送NTP请求帧
 */
void ntp_network::sendData()
{
    QByteArray arr = toNtpPacket();
    qint64 len = m_socket->write(arr);
    if(len != arr.count())
    {
        qWarning() << "发送NTP请求帧失败:" << arr.toHex(' ');
    }
}


/**
 * @brief     将QByteArray类型时间戳数据转换为整形并且进行大小端转换
 * @param bt
 * @return
 */
quint32 byteToUInt32(QByteArray bt) {
    if(bt.count() != 4) return 0;

    quint32 value;
    memcpy(&value, bt.data(), 4);

    return qToBigEndian(value);       // 大端转小端
}

/**
 * @brief      将Ntp时间戳转换成QDateTime可用的时间戳
 * @param bt
 * @return
 */
qint64 byte64ToMillionSecond(QByteArray bt) {
    qint64 second = byteToUInt32(bt.left(4));
    qint64 millionSecond = byteToUInt32(bt.mid(4, 4));
    return (second * 1000L) + ((millionSecond * 1000L) >> 32);
}

/**
 * @brief 接收返回的NTP数据帧并解析
 */
void ntp_network::on_readData()
{
    QElapsedTimer timer;       // 统计数据解析消耗的时间
    timer.start();

    QByteArray buf = m_socket->readAll();
    qint64 currentLocalTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch();        // 客户端接收到响应报文时的时间戳 T4
    if(buf.count() < 48)          // Ntp协议帧长度为48字节
    {
        return;
    }

    QDateTime epoch(QDate(1900, 1, 1), QTime(0, 0, 0));           // ntp时间计时从1900年开始
    QDateTime unixStart(QDate(1970, 1, 1), QTime(0, 0, 0));       // UNIX操作系统考虑到计算机产生的年代和应用的时限综合取了1970年1月1日作为UNIX TIME的纪元时间(开始时间)
    qint64 unixDiff = epoch.msecsTo(unixStart);

    // 解析ntp协议中的时间
    qint64 referenceTimestamp = byte64ToMillionSecond(buf.mid(16, 8)) - unixDiff;           // 参考时间戳
    qint64 originTimestamp;                                                                 // 原始时间戳    T1
    memcpy(&originTimestamp, buf.mid(24, 8), 8);
    qint64 receiveTimestamp = byte64ToMillionSecond(buf.mid(32, 8)) - unixDiff;             // 接收时间戳   T2
    qint64 translateTimestamp = byte64ToMillionSecond(buf.mid(40, 8)) - unixDiff;           // 传送时间戳   T3

    QDateTime dateTime;

#if 0
    qDebug() << "-----------NTP协议中包含的所有时间-----------";
    dateTime.setMSecsSinceEpoch(referenceTimestamp);
    qDebug() << "参考时间戳:  " << dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    dateTime.setMSecsSinceEpoch(originTimestamp);
    qDebug() << "原始时间戳T1:" << dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    dateTime.setMSecsSinceEpoch(receiveTimestamp);
    qDebug() << "接收时间戳T2:" << dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    dateTime.setMSecsSinceEpoch(translateTimestamp);
    qDebug() << "传送时间戳T3:" << dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    dateTime.setMSecsSinceEpoch(currentLocalTimestamp);
    qDebug() << "本地时间戳T4:" << dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    qDebug() << "------------------------------------------";
#endif

    QString strTime;
#if 1         // 计算方式1:时间差offset=((T2-T1)+(T3-T4))/2      实际时间=程序处理时间(timer.elapsed()) + 接收数据时间T4 + 客户端与服务端的时间差(offset)
    qint64 currentLocalTimestamp1 = timer.elapsed() + currentLocalTimestamp + qint64((receiveTimestamp - originTimestamp + translateTimestamp - currentLocalTimestamp) / 2);

    dateTime.setMSecsSinceEpoch(currentLocalTimestamp1);
    strTime = dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
    emit updateData(strTime);
#else        // 计算方式2:往返时延Delay=(T4-T1)-(T3-T2)            实际时间=程序处理时间(timer.elapsed()) + 服务器数据发出时间(T3)+ 通信时延(Delay)
    qint64 currentLocalTimestamp2 = timer.elapsed() + translateTimestamp + (((currentLocalTimestamp - originTimestamp) - (translateTimestamp - receiveTimestamp)) / 2);
    dateTime.setMSecsSinceEpoch(currentLocalTimestamp2);
    strTime = dateTime.toString("yyyy-MM-dd HH:mm:ss zzz");
#endif
    qDebug() << strTime;
    setDateTime(dateTime);
}


/**
 * @brief           设置系统时间(注意:这个功能需要使用管理员权限或者超级用户权限)
 * @param dateTime
 */
void ntp_network::setDateTime(QDateTime& dateTime)
{
    QDate date = dateTime.date();
    QTime time = dateTime.time();
#ifdef Q_OS_WIN

    SYSTEMTIME system_time = {0};
    memset(&system_time, 0, sizeof(SYSTEMTIME));
    system_time.wYear = date.year();
    system_time.wMonth = date.month();
    system_time.wDay = date.day();
    system_time.wHour = time.hour();
    system_time.wMinute = time.minute();
    system_time.wSecond = time.second();
    system_time.wMilliseconds = time.msec();
    if (SetLocalTime(&system_time))            // 仅限于管理员。
    {
        emit updateData("设置时间成功!");
    }
    else
    {
        emit updateData("设置时间失败!");
    }
#endif

#ifdef Q_OS_LINUX
    struct tm tptr;
    struct timeval tv;

    tptr.tm_year = date.year() - 1900;            // 这里必须-1900,否则设置不成功
    tptr.tm_mon = date.month() - 1;               // [0-11]
    tptr.tm_mday = date.day();
    tptr.tm_hour = time.hour();
    tptr.tm_min = time.minute()+1;
    tptr.tm_sec = time.second();

    tv.tv_sec = mktime(&tptr);                    // 将tptr赋值给tv_sec
    tv.tv_usec = time.msec() * 1000;              // 设置微秒值

    if (0 == settimeofday(&tv, NULL))            // 仅限于超级用户, 使用sudo ./NtpClient
    {
        emit updateData("设置时间成功!");
        qDebug()<<"设置时间成功!";
    }
    else
    {
        emit updateData("设置时间失败!");
        qDebug()<<"设置时间失败!";
    }
#endif
}
 

#ifndef NTP_NETWORK_H
#define NTP_NETWORK_H

#include <QObject>
#include <QWidget>
class QUdpSocket;

#if 0   // NTP协议帧(未使用)
typedef struct
{
    char  LI_VN_Mode;
    char  Stratum;
    char  Poll;
    char  Precision;
    int  RootDelay;
    int  RootDispersion;
    int  ReferenceIdentifier;
    quint64  ReferenceTimeStamp;    // 系统时钟最后一次被设定或更新的时间
    quint64   OriginateTimeStamp;    // NTP请求报文离开发送端时发送端的本地时间
    quint64   ReceiveTimeStamp;      // NTP请求报文到达Server端时接收端的本地时间。
    quint64   TransmitTimeStamp;     // 发送时间戳,客户端发送时填写,server接收到后会将TransmitTimeStamp值写入OriginateTimeStamp,然后NTP应答报文离开Server时在OriginateTimeStamp的本地时间。
}NtpPacket;
#endif

class ntp_network : public QObject
{
    Q_OBJECT
   public:
       explicit ntp_network(QObject *parent = nullptr);

       void connectServer(QString url);        // 连接Ntp服务
       void close();
       void getTime();

   signals:
       void updateData(const QString& time);          // 添加显示到界面上文本框中的信息

   private slots:
       void on_connected();
       void on_readData();
       void sendData();
       void setDateTime(QDateTime& dateTime);

   private:
       QUdpSocket* m_socket = nullptr;
};

#endif // NTP_NETWORK_H
 

ntp_network *ntp_time=new ntp_network(this);

    ntp_time->connectServer("ntp.aliyun.com");

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌风_lwp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值