Linux下 C/C++ NTP网络时间协议详解

NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议。它是通过网络在计算机系统之间进行时钟同步的网络协议。NTP 在公共互联网上通常能够保持时间延迟在几十毫秒以内的精度,并在理想条件下,它能在局域网下达到低于一毫秒的延迟精度。它使用用户数据报协议(UDP)在端口 123 上发送和接受时间戳。它是个 C/S 架构的应用程序。

NTP工作原理

NTP的基本工作原理如图所示。Device A和Device B通过网络相连,它们都有自己独立的系统时钟,需要通过NTP实现各自系统时钟的自动同步。为便于理解,作如下假设:
 ·在Device A和Device B的系统时钟同步之前,Device A的时钟设定为10:00:00am,Device B的时钟设定为11:00:00am。
 ·Device B作为NTP时间服务器,即Device A将使自己的时钟与Device B的时钟同步。
 ·NTP报文在Device A和Device B之间单向传输所需要的时间为1秒。

系统时钟同步的工作过程如下:
 ·Device A发送一个NTP报文给Device B,该报文带有它离开Device A时的时间戳,该时间戳为10:00:00am(T1)。
 ·当此NTP报文到达Device B时,Device B加上自己的时间戳,该时间戳为11:00:01am(T2)。
 ·当此NTP报文离开Device B时,Device B再加上自己的时间戳,该时间戳为11:00:02am(T3)。
 ·当Device A接收到该响应报文时,Device A的本地时间为10:00:03am(T4)。
至此,Device A已经拥有足够的信息来计算两个重要的参数:
 ·NTP报文的往返时延Delay=(T4-T1)-(T3-T2)=2秒。
 ·Device A相对Device B的时间差offset=((T2-T1)+(T3-T4))/2=1小时。
这样,Device A就能够根据这些信息来设定自己的时钟,使之与Device B的时钟同步。
以上内容只是对NTP工作原理的一个粗略描述.

/**
  * T1,客户端发送请求时的 本地系统时间戳;
  * T2,服务端接收到客户端请求时的 本地系统时间戳;
  * T3,服务端发送应答数据包时的 本地系统时间戳;
  * T4,客户端接收到服务端应答数据包时的 本地系统时间戳。
  */

NTP 报文格式

/*
 * Dissecting NTP packets version 3 and 4 (RFC5905, RFC2030, RFC1769, RFC1361,
 * RFC1305).
 *
 * Those packets have simple structure:
 *                      1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                          Root Delay                           |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                       Root Dispersion                         |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                    Reference Identifier                       |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                   Reference Timestamp (64)                    |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                   Originate Timestamp (64)                    |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                    Receive Timestamp (64)                     |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                    Transmit Timestamp (64)                    |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                 Key Identifier (optional) (32)                |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                 Message Digest (optional) (128/160)           |
 * |                                                               |
 * |                                                               |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * NTP timestamps are represented as a 64-bit unsigned fixed-point number,
 * in seconds relative to 0h on 1 January 1900. The integer part is in the
 * first 32 bits and the fraction part in the last 32 bits.
 *
 *
 * NTP Control messages as defined in version 2, 3 and 4 (RFC1119, RFC1305) use
 * the following structure:
 *                      1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |00 | VN  | 110 |R E M| OpCode  |           Sequence            |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |            Status             |        Association ID         |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |            Offset             |             Count             |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                                                               |
 * |                     Data (468 octets max)                     |
 * |                                                               |
 * |                               |        Padding (zeros)        |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                 Authenticator (optional) (96)                 |
 * |                                                               |
 * |                                                               |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * Not yet implemented: complete dissection of TPCTRL_OP_SETTRAP,
 * NTPCTRL_OP_ASYNCMSG, NTPCTRL_OP_UNSETTRAPSETTRAP Control-Messages
 *
 */

NTP Version 3
NTP Version 4

它的字段含义参考如下:

LI 闰秒标识器,占用2个bit
VN 版本号,占用3个bits,表示NTP的版本号,现在为3
Mode 模式,占用3个bits,表示模式
stratum(层),占用8个bits
Poll 测试间隔,占用8个bits,表示连续信息之间的最大间隔
Precision 精度,占用8个bits,,表示本地时钟精度
Root Delay根时延,占用8个bits,表示在主参考源之间往返的总共时延
Root Dispersion根离散,占用8个bits,表示在主参考源有关的名义错误
Reference Identifier参考时钟标识符,占用8个bits,用来标识特殊的参考源
参考时间戳,64bits时间戳,本地时钟被修改的最新时间。
原始时间戳,客户端发送的时间,64bits。
接受时间戳,服务端接受到的时间,64bits。
传送时间戳,服务端送出应答的时间,64bits。
认证符(可选项)

NTP的工作模式

NTP支持以下几种工作模式:

 1.客户端/服务器模式
 2.对等体模式
 3.广播模式
 4.组播模式

用户可以根据需要选择一种或几种工作模式进行时间同步,我们主要讲解客户端/服务器模式。

工作过程:

客户端上需要手工指定NTP服务器的地址。客户端向NTP服务器发送NTP时间同步报文。NTP服务器收到报文后会自动工作在服务器模式,并回复应答报文如果客户端可以从多个时间服务器获取时间同步,则客户端收到应答报文后,进行时钟过滤和选择,并与优选的时钟进行时间同步

一些常用的 NTP 服务器地址:

ntp.tencent.com
ntp1.tencent.com
ntp2.tencent.com
ntp3.tencent.com
ntp4.tencent.com
ntp5.tencent.com
ntp.aliyun.com
ntp1.aliyun.com
ntp2.aliyun.com
ntp3.aliyun.com
ntp4.aliyun.com
ntp5.aliyun.com
ntp6.aliyun.com
ntp7.aliyun.com
time.edu.cn
s2c.time.edu.cn
s2f.time.edu.cn

NTP客户端连接服务器获取网络时间戳

typedef enum xntp_mode_t
{
    ntp_mode_unknow     = 0,  ///< 未定义
    ntp_mode_initiative = 1,  ///< 主动对等体模式
    ntp_mode_passive    = 2,  ///< 被动对等体模式
    ntp_mode_client     = 3,  ///< 客户端模式
    ntp_mode_server     = 4,  ///< 服务器模式
    ntp_mode_broadcast  = 5,  ///< 广播模式或组播模式
    ntp_mode_control    = 6,  ///< 报文为 NTP 控制报文
    ntp_mode_reserved   = 7,  ///< 预留给内部使用
} xntp_mode_t;


static x_void_t output_tu(x_cstring_t xszt_info, xtime_vnsec_t xtm_vnsec)
{
    printf("%s : ", xszt_info);
    output_ns(xtm_vnsec);
    printf("\n");
}

/**********************************************************/
/**
 *打上信息标号,输出 NTP 时间戳 的 具体时间信息。
 */
static x_void_t output_tm(x_cstring_t xszt_info, xtime_stamp_t * xtm_stamp)
{
    printf("%s : ", xszt_info);
    output_ts(xtm_stamp);
    printf("\n");
}

...
xtime_vnsec_t ntpcli_get_time(
                    x_cstring_t xszt_host,
                    x_uint16_t xut_port,
                    x_uint32_t xut_tmout)
{
    x_int32_t     xit_errno = EPERM;
    xtime_vnsec_t xtm_vnsec = XTIME_INVALID_VNSEC;
    xntp_cliptr_t xntp_this = X_NULL;


    do
    {
        xntp_this = ntpcli_open();
        if (X_NULL == xntp_this)
        {
            break;
        }

        xit_errno = ntpcli_config(xntp_this, xszt_host, xut_port);
        if (0 != xit_errno)
        {
            errno = xit_errno;
            break;
        }

        xtm_vnsec = ntpcli_req_time(xntp_this, xut_tmout);
    } while (0);

    if (X_NULL != xntp_this)
    {
        ntpcli_close(xntp_this);
        xntp_this = X_NULL;
    }

    return xtm_vnsec;
}
/**********************************************************/
/**
 *初始化 NTP 的请求数据包。
 */
static x_void_t ntp_init_req_packet(xntp_pack_t * xnpt_dptr)
{
...

    xnpt_dptr->xct_lvmflag   = NTP_LI_VN_MODE(0, 3, ntp_mode_client);
    xnpt_dptr->xct_stratum   = 0;
    xnpt_dptr->xct_ppoll     = 4;
    xnpt_dptr->xct_percision = (x_char_t)(-6);

    xnpt_dptr->xut_rootdelay = (1 << 16);
    xnpt_dptr->xut_rootdisp  = (1 << 16);
    xnpt_dptr->xut_refid     = 0;

    xnpt_dptr->xtms_reference.xut_seconds  = 0;
    xnpt_dptr->xtms_reference.xut_fraction = 0;
    xnpt_dptr->xtms_originate.xut_seconds  = 0;
    xnpt_dptr->xtms_originate.xut_fraction = 0;
    xnpt_dptr->xtms_receive  .xut_seconds  = 0;
    xnpt_dptr->xtms_receive  .xut_fraction = 0;
    xnpt_dptr->xtms_transmit .xut_seconds  = 0;
    xnpt_dptr->xtms_transmit .xut_fraction = 0;
}

/**********************************************************/
/**
 * 发送 NTP 请求,获取服务器时间戳。
 */
xtime_vnsec_t ntpcli_req_time(xntp_cliptr_t xntp_this, x_uint32_t xut_tmout)
{
    x_int32_t xit_errno = EPERM;

    // 参数验证

    if (X_NULL == xntp_this)
    {
        errno = EINVAL;
        return XTIME_INVALID_VNSEC;
    }

    if (name_is_ipv4(xntp_this->xszt_host, X_NULL))
        xit_errno = ntpcli_get_4T(xntp_this, xntp_this->xszt_host, xut_tmout);
    else
        xit_errno = ntpcli_get_4T_by_name(xntp_this, xut_tmout);

    if (0 != xit_errno)
    {
        errno = xit_errno;
        return XTIME_INVALID_VNSEC;
    }
    return ntp_calc_4T(xntp_this->xtm_4time);
}

/** 常用的 NTP 服务器地址列表 */
x_cstring_t xszt_host[] =
{
    "ntp.tencent.com" ,
    "ntp1.tencent.com",
    "ntp2.tencent.com",
    "ntp3.tencent.com",
    "ntp4.tencent.com",
    "ntp5.tencent.com",
    "ntp1.aliyun.com" ,
    "ntp2.aliyun.com" ,
    "ntp3.aliyun.com" ,
    "ntp4.aliyun.com" ,
    "ntp5.aliyun.com" ,
    "ntp6.aliyun.com" ,
    "ntp7.aliyun.com" ,
    "time.edu.cn"     ,
    "s2c.time.edu.cn" ,
    "s2f.time.edu.cn" ,
    "s2k.time.edu.cn" ,
    X_NULL
};

/** 网络地址(IP 或 域名)类型 */
typedef x_char_t x_host_t[TEXT_LEN_256];

/**
 * 命令选项的各个参数。
 */
typedef struct xopt_args_t
{
    x_bool_t   xbt_usage; ///< 是否显示帮助信息
    x_uint16_t xut_port;  ///< NTP 服务器端口号(默认值为 123)
    x_host_t   xntp_host; ///< NTP 服务器地址
    x_int32_t  xit_rept;  ///< 请求重复次数(默认值为 1)
    x_uint32_t xut_tmout; ///< 网络请求的超时时间(单位为 毫秒,默认值取 3000)
} xopt_args_t;

/** 简单的判断 xopt_args_t 的有效性 */
#define XOPT_VALID(xopt) (('\0' != (xopt).xntp_host[0]) && ((xopt).xit_rept > 0))



/**********************************************************/
/**
 * 字符串忽略大小写的比对操作。
 */
static x_int32_t xstr_icmp(x_cstring_t xszt_lcmp, x_cstring_t xszt_rcmp)
{
    x_int32_t xit_lvalue = 0;
    x_int32_t xit_rvalue = 0;

    if (xszt_lcmp == xszt_rcmp)
        return 0;
    if (X_NULL == xszt_lcmp)
        return -1;
    if (X_NULL == xszt_rcmp)
        return 1;

    do
    {
        if (((xit_lvalue = (*(xszt_lcmp++))) >= 'A') && (xit_lvalue <= 'Z'))
            xit_lvalue -= ('A' - 'a');

        if (((xit_rvalue = (*(xszt_rcmp++))) >= 'A') && (xit_rvalue <= 'Z'))
            xit_rvalue -= ('A' - 'a');

    } while (xit_lvalue && (xit_lvalue == xit_rvalue));

    return (xit_lvalue - xit_rvalue);
}

/**********************************************************/
/**
 *显示应用程序的 命令行格式。
 */
x_void_t usage(x_cstring_t xszt_app)
{
    x_int32_t xit_iter = 1;

    printf("Usage:\n %s [-h] [-n <number>] -s <host> [-p <port>] [-t <msec>]\n", xszt_app);
    printf("\t-h          Output usage.\n");
    printf("\t-n <number> The times of repetition.\n");
    printf("\t-s <host>   The host of NTP server, IP or domain.\n");
    printf("\t-p <port>   The port of NTP server, default 123.\n");
    printf("\t-t <msec>   Network request timeout in milliseconds, default 3000.\n");

    printf("\nCommon NTP server:\n");

    for (xit_iter = 0; X_NULL != xszt_host[xit_iter]; ++xit_iter)
    {
        printf("\t%s\n", xszt_host[xit_iter]);
    }

    printf("\n");
}

/**********************************************************/
/**
 * 从命令行中,提取工作的选项参数信息。
 */
x_void_t get_opt(x_int32_t xit_argc, x_cstring_t xszt_argv[], xopt_args_t * xopt_args)
{
...

    for (; xit_iter < xit_argc; ++xit_iter)
    {
        if (!xopt_args->xbt_usage && (0 == xstr_icmp("-h", xszt_argv[xit_iter])))
        {
            xopt_args->xbt_usage = X_TRUE;
        }
        else if (0 == xstr_icmp("-s", xszt_argv[xit_iter]))
        {
            if ((xit_iter + 1) < xit_argc)
            {
...
            }
        }
        else if (0 == xstr_icmp("-n", xszt_argv[xit_iter]))
        {
            if ((xit_iter + 1) < xit_argc)
                xopt_args->xit_rept = atoi(xszt_argv[++xit_iter]);
        }
        else if (0 == xstr_icmp("-p", xszt_argv[xit_iter]))
        {
            if ((xit_iter + 1) < xit_argc)
                xopt_args->xut_port = (x_uint16_t)atoi(xszt_argv[++xit_iter]);
        }
        else if (0 == xstr_icmp("-t", xszt_argv[xit_iter]))
        {
            if ((xit_iter + 1) < xit_argc)
                xopt_args->xut_tmout = (x_uint32_t)atoi(xszt_argv[++xit_iter]);
        }
    }
}

int main(int argc, char * argv[])
{
...
    do
    {

        get_opt(argc, argv, &xopt_args);
        if (!XOPT_VALID(xopt_args))
        {
            usage(argv[0]);
            break;
        }

        if (xopt_args.xbt_usage)
        {
            usage(argv[0]);
        }

        xntp_this = ntpcli_open();
        if (X_NULL == xntp_this)
        {
            printf("ntpcli_open() return X_NULL, errno : %d\n", errno);
            break;
        }

        ntpcli_config(xntp_this, xopt_args.xntp_host, xopt_args.xut_port);

        for (xit_iter = 0; xit_iter < xopt_args.xit_rept; ++xit_iter)
        {
            xtm_vnsec = ntpcli_req_time(xntp_this, xopt_args.xut_tmout);
            if (XTMVNSEC_IS_VALID(xtm_vnsec))
            {
                xtm_ltime = time_vnsec();
                xtm_descr = time_vtod(xtm_vnsec);
                xtm_local = time_vtod(xtm_ltime);

                printf("\n[%d] %s:%d : \n",
                       xit_iter + 1,
                       xopt_args.xntp_host,
                       xopt_args.xut_port);
                printf("\tNTP response : [ %04d-%02d-%02d %d %02d:%02d:%02d.%03d ]\n",
                       xtm_descr.ctx_year  ,
                       xtm_descr.ctx_month ,
                       xtm_descr.ctx_day   ,
                       xtm_descr.ctx_week  ,
                       xtm_descr.ctx_hour  ,
                       xtm_descr.ctx_minute,
                       xtm_descr.ctx_second,
                       xtm_descr.ctx_msec  );

                printf("\tLocal time   : [ %04d-%02d-%02d %d %02d:%02d:%02d.%03d ]\n",
                       xtm_local.ctx_year  ,
                       xtm_local.ctx_month ,
                       xtm_local.ctx_day   ,
                       xtm_local.ctx_week  ,
                       xtm_local.ctx_hour  ,
                       xtm_local.ctx_minute,
                       xtm_local.ctx_second,
                       xtm_local.ctx_msec  );

                printf("\tDeviation    : %lld us\n",
                       ((x_int64_t)(xtm_ltime - xtm_vnsec)) / 10LL);
            }
            else
            {
                printf("\n[%d] %s:%d : errno = %d\n",
                       xit_iter + 1,
                       xopt_args.xntp_host,
                       xopt_args.xut_port,
                       errno);
            }
        }

        
    } while (0);

    if (X_NULL != xntp_this)
    {
        ntpcli_close(xntp_this);
        xntp_this = X_NULL;
    }
...
    return 0;
}
...

运行结果:

If you need the complete source code, please add the WeChat number (c17865354792)

在客户端/服务器模式中,客户端向服务器发送时钟同步报文,报文中的Mode字段设置为3(客户模式)。服务器端收到报文后会自动工作在服务器模式,并发送应答报文,报文中的Mode字段设置为4(服务器模式)。客户端收到应答报文后,进行时钟过滤和选择,并同步到优选的服务器。

在该模式下,客户端能同步到服务器,而服务器无法同步到客户端。

  • tcpdump抓包分析

NTP协议应用于分布式时间服务器和客户端之间,实现客户端和服务器的时间同步,从而使网络内所有设备的时钟基本保持一致。下面的报文是客户端连接服务器时产生的交互过程。

  • NTP协议解析

If you need the complete source code, please add the WeChat number (c17865354792)

总结

NTP客户端启动与NTP服务器的时间请求交换。交换的结果是,客户端能够计算链路延迟及其本地偏移,并调整其本地时钟以匹配服务器计算机上的时钟。

Welcome to follow WeChat official account【程序猿编码

参考:1.RFC 1305
2.http://ntp.neu.edu.cn/archives/92/
3.http://ntp.neu.edu.cn/archives/95

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值