C++使用Boost实现Network Time Protocol(NTP)客户端

笔者机器上安装了两个系统,一个Linux Ubuntu,一个Windows8.1。让人感到郁闷的是,每次从Ubuntu重启进入Windows时,系统时间总是少了8个小时,每次都要用Windows的时间程序进行同步,也就是下面这个东西:

这个东西其实就是一个NTP Client,从Internet上选择一台NTP Server,获取UTC时间,然后设置本地时间。

于是我想自己实现一个这样的程序,先百度一下吧,网上有很多关于NTP的资料和实现代码,大多是单一平台的,不能跨平台

,下面给几个参考:

http://blog.csdn.net/loongee/article/details/24271129

http://blog.csdn.net/chexlong/article/details/6963541

http://www.cnblogs.com/TianFang/archive/2011/12/20/2294603.html

本文使用boost的Asio来跨平台实现NTP Client.

准备

1. 最新的boost库,本文使用的是1.56.0版本

要用到里面的ASIO网络库

2. IDE是Visual Studio 2013 with Update3

笔者是版本帝

3. WireShark也是最新的1.12.1版本

用来分析Windows自带的NTP Client

NTP Packet分析

这里我们分析的正是上图那个程序,点击立即更新,会发送NTP的请求包,下面是Wireshark的抓包结果:

可以得到下面一些信息:

  1. NTP时间同步分两个过程,一个Request,一个Response
  2. 这里的NTP Server的IP地址是129.6.15.28
  3. 程序没有进行DNS解析,可能是直接保存了IP地址
  4. NTP服务的端口号是123,Client也使用了123端口,后来发现Client不是一定要使用123端口的
  5. NTP协议是构建在UDP传输协议上的应用协议
  6. 这里使用V3版的NTP协议,目前还有v4

好了,有了关于NTP协议的一些基本信息,我们再来看看应用层的详细信息:

Response包:

分了很多字段,关于每个字段的含义请参考上面给出的链接,本文主要讲实现。这里Reference Timestamp就是Request包发送的Timestamp,而Origin,Receive,Transmit都是从Server返回回来的时间,后三个时间都相差非常小,因此方便一点,我们取最后一个Transmit Timestamp作为结果。

?编码?

boost里面相关库的编译可以参考官方的文档,里面有非常简单的例子。

1. 需要的头文件和名字空间

#include <iostream>
#include "boost/asio.hpp"
#include "boost/date_time/posix_time/posix_time.hpp"

using namespace boost::posix_time;
using namespace boost::asio::ip;

2. NtpPacket的构造

class NtpPacket {
public:
    NtpPacket() {
        _rep._flags = 0xdb;
        //    11.. ....    Leap Indicator: unknown
        //    ..01 1...    NTP Version 3
        //    .... .011    Mode: client
        _rep._pcs = 0x00;//unspecified
        _rep._ppt = 0x01;
        _rep._pcp = 0x01;
        _rep._rdy = 0x01000000;//big-endian
        _rep._rdn = 0x01000000;
        _rep._rid = 0x00000000;
        _rep._ret = 0x0;
        _rep._ort = 0x0;
        _rep._rct = 0x0;
        _rep._trt = 0x0;
    }

    friend std::ostream& operator<<(std::ostream& os, const NtpPacket& ntpacket) {
        return os.write(reinterpret_cast<const char *>(&ntpacket._rep), sizeof(ntpacket._rep));
    }

    friend std::istream& operator>>(std::istream& is, NtpPacket& ntpacket) {
        return is.read(reinterpret_cast<char*>(&ntpacket._rep), sizeof(ntpacket._rep));
    }

public:
#pragma pack(1)
    struct NtpHeader {
        uint8_t _flags;//Flags
        uint8_t _pcs;//Peer Clock Stratum
        uint8_t _ppt;//Peer Polling Interval
        uint8_t _pcp;//Peer Clock Precision
        uint32_t _rdy;//Root Delay
        uint32_t _rdn;//Root Dispersion
        uint32_t _rid;//Reference ID
        uint64_t _ret;//Reference Timestamp
        uint64_t _ort;//Origin Timestamp
        uint64_t _rct;//Receive Timestamp
        uint64_t _trt;//Transmit Timestamp
    };
#pragma pack()
    NtpHeader _rep;
};

这里为了方便存取就没有把struct放到private中,需要注意的是结构体各个字段的顺序和需要进行内存1字节对齐,即使用:

#pragma pack(1)

内存对齐在网络编程中十分重要,他会直接影响Packet的内容,关于内存对齐可以参考:

http://www.cppblog.com/cc/archive/2006/08/01/10765.html

NTP请求包中最重要的是flags,里面存有版本信息等直接影响协议工作的内容,因此不能搞错了。

两个operator重载用来方便读写Packet数据。

再来看看Client类的实现,Client类的主要任务就是发送和接受NTP包,并返回最后那个64bit的Timestamp。

class NtpClient {
public:
    NtpClient(const std::string& serverIp)
        :_socket(io), _serverIp(serverIp) {
    }

    time_t getTime() {
        if (_socket.is_open()) {
            _socket.shutdown(udp::socket::shutdown_both, _ec);
            if (_ec) {
                std::cout << _ec.message() << std::endl;
                _socket.close();
                return 0;
            }
            _socket.close();
        }
        udp::endpoint ep(boost::asio::ip::address_v4::from_string(_serverIp), NTP_PORT);
        NtpPacket request;
        std::stringstream ss;
        std::string buf;
        ss << request;
        ss >> buf;
        _socket.open(udp::v4());
        _socket.send_to(boost::asio::buffer(buf), ep);
        std::array<uint8_t, 128> recv;
        size_t len = _socket.receive_from(boost::asio::buffer(recv), ep);
        uint8_t* pBytes = recv.data();
        /****dump hex data
        for (size_t i = 0; i < len; i++) {
        if (i % 16 == 0) {
        std::cout << std::endl;
        }
        else {
        std::cout << std::setw(2) << std::setfill(‘0‘)
        << std::hex << (uint32_t) pBytes[i];
        std::cout << ‘ ‘;
        }
        }
        ****/
        time_t tt;
        uint64_t last;
        uint32_t seconds;
        /****get the last 8 bytes(Transmit Timestamp) from received packet.
        std::memcpy(&last, pBytes + len - 8, sizeof(last));
        ****create a NtpPacket*/
        NtpPacket resonpse;
        std::stringstream rss;
        rss.write(reinterpret_cast<const char*>(pBytes), len);
        rss >> resonpse;
        last = resonpse._rep._trt;
        //
        reverseByteOrder(last);
        seconds = (last & 0x7FFFFFFF00000000) >> 32;
        tt = seconds + 8 * 3600 * 2 - 61533950;
        return tt;
    }

private:
    const uint16_t NTP_PORT = 123;
    udp::socket _socket;
    std::string _serverIp;
    boost::system::error_code _ec;
};

注意几个地方:

1. udp::socket是boost里面使用udp协议的套接字,他的构造需要一个io_service,io_service可以直接在全局区进行声明:

boost::asio::io_service io;

2. 创建一个endpoint用来表示NTP Server的地址:

udp::endpoint ep(boost::asio::ip::address_v4::from_string(_serverIp), NTP_PORT);

向这个ep send_to,并从这个ep receive_from数据包。

3. time_t的定义如下:

typedef __time64_t time_t;      /* time value */
typedef __int64 __time64_t;     /* 64-bit time value */

也就是说这个time_t其实就是一个64bit的int,我们可以用uint64_t这个类型与之互换,他可以用来表示一个Timestamp。

4. 获取最后8字节内容有两种方式,一种是直接复制pBytes的内存,一种是构造NtpPacket,然后取成员,这里选择后者易于理解。

5. 字节序的问题

网络字节序都是大端模式,需要进行转换,由于仅仅需要最后那个uint64_t所以我写了一个针对64bit的字节序转换函数:

static void reverseByteOrder(uint64_t &in) {
    uint64_t rs = 0;
    int len = sizeof(uint64_t);
    for (int i = 0; i < len; i++) {
        std::memset(reinterpret_cast<uint8_t*>(&rs) + len - 1 - i
                    , static_cast<uint8_t> ((in & 0xFFLL << (i * 8)) >> i * 8)
                    , 1);
    }
    in = rs;
}

最后一个64bit内容的高32位存了UTC秒数,所以需要取出来,然后再转换为本地时区的秒数。

seconds = (last & 0x7FFFFFFF00000000) >> 32;

注意最高位是不能取的,尽管是unsigned,至于为什么要- 61533950这个是笔者在自己电脑上尝试出来的,找了很多资料不

知是哪里的问题,还请各位知道的读者告诉我哈。

再来看看主函数:

int main(int argc, char* agrv[]) {
    NtpClient ntp("129.6.15.28");
    int n = 5;
    while (n--) {
        time_t tt = ntp.getTime();
        boost::posix_time::ptime utc = from_time_t(tt);
        std::cout << "Local Timestamp:" << time(0) << ‘\t‘ << "NTP Server:" << tt << "(" << to_simple_string(utc) << ")" << std::endl;
        Sleep(10);
    }
    return 0;
}

这里进行5次NTP请求,并使用boost的to_simple_string转换UTC时间打印结果。

大概是这种效果:

收尾

同步时间一般都会想到找一个http api接口,本文主要是用了NTP协议。为了跨平台,上面的代码尽可能避免使用平台相关的宏和函数,只要稍作修改就能在各种平台下执行,也得益于boost这个强悍的准标准库给开发者带来的便利。

时间: 2024-10-25 19:29:21

C++使用Boost实现Network Time Protocol(NTP)客户端的相关文章

Linux NTP配置详解 (Network Time Protocol)

Network Time Protocol (NTP) 也是RHCE新增的考试要求. 学习的时候也顺便复习了一下如何设置Linux的时间,现在拿出来和大家分享 设置NTP服务器不难但是NTP本身是一个很复杂的协议. 这里只是简要地介绍一下实践方法和上次一样,下面的实验都在RHEL5上运行 1. 时间和时区 如果有人问你说现在几点? 你看了看表回答他说晚上8点了. 这样回答看上去没有什么问题,但是如果问你的这个人在欧洲的话那么你的回答就会让他很疑惑,因为他那里还太阳当空呢. 这里就有产生了一个如何

NTP(Network Time Protocol) 服务器:时间服务器

NTP(Network Time Protocol) 服务器:时间服务器GMT(Greenwich Mean Time,GMT 时间):格林威治标准时间夏季节约时间(或称日光节约时间):daylight savings(DST)UTC(Coordinated Universal Time ) :协和标准时间最标准时间是由原子钟计算出来的,根据原子震荡周期UTC与GMT时间由于计时方式不同,相差有16分钟 软件时钟:由Linux操作系统根据1970/01/01开始计算的总秒数:硬件时钟:主机硬件系

在CentOS7.4上配置NTP客户端

在CentOS7.4上配置NTP客户端 1.检查是否连通NTP服务器[[email protected] ~]# ntpdate 192.168.1.10013 Dec 15:10:12 ntpdate[9146]: step time server 192.168.1.100 offset -2289.098141 sec[[email protected] ~]# dateFri Dec 13 15:10:19 CST 2019 2.设置NTP客户端[[email protected] ~]

NTP工作机制及时间同步的方法

Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源做同步化,它可以提供高精准度的时间校正,且可用加密确认的方式来防止恶毒的协议攻击. NTP提供准确时间,首先要有准确的时间来源,这一时间就是是国际标准时间UTC. NTP获得UTC的时间来源可以是原子钟.天文台.卫星,也可以从Internet上获取.这样就有了准确而可靠的时间源.时间按NTP服务器的等级传播.按照离外部UTC源的远近将所有服务器归入不同的Stratum层.Str

时间服务器:NTP服务器

GMT时间 经度为零的地点在英国『格林威治』这个城市所在的纵剖面上 格林威治时间为标准时间 (Greenwich Mean Time, GMT 时间) 格林威治以东的区域时间是比较快的(+小时) 因此中国在东八区本地时间 (local time) 会比 GMT 时间快 8 小时 (GMT + 8) 1880 年代的时间标准是以 GMT 时间为主 夏季节约时间(daylight savings) 在夏天的时候,白天的时间会比较长,所以为了节约用电, 因此在夏天的时候某些横跨两个时区的地区会将他们的

The Reflection And Amplification Attacks &amp;&amp; NTP Reply Flood Attack Based On NTP

目录 1. NTP简介 2. NTP协议格式 3. NTP Reflect反射漏洞和NTP协议的关系 4. 漏洞触发的前提和攻击需要的步骤 5. 针对漏洞的攻防思考 1. NTP简介 Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒),且可介由加密确认的方式来防止恶毒的协议攻击. NTP提供准确时间,首先要有准确的时

CentOS6.5系统搭建NTP服务器

在进入到我们的主题之前首先我们可以简单了解一下这几个名词 Atomic Clock: 现在计算时间最准确的是使用 原子震荡周期 所计算的物理时钟(Atomic Clock),因此也被定义为标准时间(International Atomic Time) UTC(coordinated Universal Time): 协和标准时间 就是利用 Atomic Clock 为基准定义出来的正确时间 (世界统一时间,世界标准时间,国际协调时间) 硬件时钟: 硬件时钟是指嵌在主板上的特殊的电路, 它的存在就

开源软件包的安装及ntp时间服务器简析

linux 系统服务篇(-)一.NTP时间服务器 network time protocol    NTP服务器的使命:使局域网内服务器(或个人pc端)的时间保持一致.二.开源软件的使用步骤:    1.安装软件服务.        (1)源码方式安装            下载,解压源码(wegt 或 rz (我用CRT)tar xf)            分析安装平台  ./configure(一般是测试软件的安装环境,看缺少哪些必要的依赖安装包)            编译软件     

各操作系统配置NTP

    一.    AIX服务器端 ?  编辑 /etc/ntp.conf 文件, 内容如下: ---------------------------- #broadcastclient server 210.72.145.44 prefer server 218.21.130.42 server 127.127.1.0 driftfile /etc/ntp.drift tracefile /etc/ntp.trace ---------------------------- 请注意文件中的 s