20190603(NTP,ntp client-C语言实现)

 

目录

1.NTP

2.简单的NTP CLIENT-C语言实现

3.用户甲参考修改:


1.NTP

NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议。

它的用途是把计算机的时钟同步到世界协调时UTC

其精度在局域网内可达0.1ms,在互联网上绝大多数的地方其精度可以达到1-50ms。


NTP要提供准确的时间,就必须有准确的时间来源,那可以用格林尼治时间吗?答案是否定的。因为格林尼治时间是以地球自转为基础的时间计量系统,但是地球每天的自转是有些不规则的,而且正在缓慢加速,因此,格林尼治时间已经不再被作为标准时间使用。

新的标准时间,是由原子钟报时的国际标准时间UTC(Universal Time Coordinated,世界协调时)。所以NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。


有了准确而可靠的的时间源,那这个时间如何传播呢?

在NTP中,定义了时间按照服务器的等级传播,按照离外部UTC源远近将所有的服务器归入不同的Stratum(层)中,例如把通过GPS(Global Positioning System,全球定位系统)取得发送标准时间的服务器叫Stratum-1的NTP服务器,而Stratum-2则从Stratum-1获取时间,Stratum-3从Stratum-2获取时间,以此类推,但Stratum层的总数限制在15以内。所有这些服务器在逻辑上形成阶梯式的架构相互连接,而Stratum-1的时间服务器是整个系统的基础,这种阶梯式的架构示意图如下所示:

计算机主机一般同多个时钟服务器连接,利用统计学的算法过滤来自不同服务器的时间,以选择最佳的路径和来源以便校正主机时间。即使在主机长时间无法与某一时钟服务器联系的情况下,NTP服务依然可以有效运转

为了防止对时钟服务器的恶意破坏,NTP使用了识别机制,检查发送来的信息是否是真正来自所宣称的时钟服务器并检查信息的返回路径,以提供对抗干扰的保护机制。 

NTP时间同步报文中包含的时间是格林威治时间,是从1900年开始计算的秒数



2.简单的NTP CLIENT-C语言实现

了解时间概念后,要做的就比较明确了

(1)发送NTP请求报文,从一个NTP服务器获取到时间

(2)更新系统时间 

本NTP客户端实现是基于NTPv3单播模式来实现的,其中参考了SNTP的实现(SNTP为NTP的简化版)。

要完成客户端的开发需要准备一些知识,比如NTP工作模式,NTP报文格式等等,大体了解完这些后才能更好地掌握整个开发过程。

NTP(Network Time Protocol),网络时间协议,应用于分布式时间服务器和客户端之间,实现客户端和服务器之间的时间同步,从而使网络内所有设备的时间基本保持一致。NTP工作于UDP的123端口

NTP报文格式

0   2     5     8               16              24              32
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Root Delay                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Root Dispersion                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Reference Identifier                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                   Reference Timestamp (64)                    |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                   Originate Timestamp (64)                    |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                   Receive Timestamp (64)                      |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                   Transmit Timestamp (64)                     |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                  Authentication (optional) (64)               |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            NTPv3报文格式
 

Leap Indicator(LI) 
闰秒指示符,这是一个2位的代码,用于警示在当天的最后一分钟里插入或删除的闰秒。取值如下:

0       无预告
1       最近一分钟有61秒
2       最近一分钟有59秒
3       警告状态(时钟未同步)

Version Number(VN) 
版本号,这是一个3位的整数,用于表示NTP的版本。

Mode 
模式,这是一个3位的整数,表示模式,值定义如下:

0           保留
1           对称主动
2           对称被动
3           客户端
4           服务器端
5           广播
6           为NTP控制控制消息
7           为自用保留

Stratum 
本地时钟层级,这是一个八位无符号整数,表示本地时钟的层级,其值定义如下:

0           未定义或难以获得
1           主要参考(如无线电时钟钟,校正的院子时钟)
2-255       第二参考(通过NTP或SNTP)

Poll 
轮询间隔,这是一个8位有符号整数,用于表示连续消息之间的最大间隔,以最接近2的N次幂来表示。如值为6表示2^6=64。

Precision 
本地时钟精度精度,这是一个8位有符号整数,用于表示本地时钟精度,以最接近2的N次幂来表示。

Root Delay 
这是一个32位有符号定点数,表示主要参考源的总往返时延,以秒为单位。该变量可以为正值和负值,具体取决于时间精度和偏移。

Root Dispersion 
这是一个32位有符号定点数,表示相对于主参考源的最大误差,以秒为单位,在15和16位之间。通常在该字段中出现的值范围为0到几百毫秒

Reference Identifier 
这是一个标识特定参考源的32位位串。在NTP版本3或版本4层级0或层级1服务器的情况下,这是一个4字符ASCII字符串,左对齐并且以0填充到32位。在NTP版本3辅助服务器中,这是参考源的32位IPv4地址。

Reference Timestamp 
这是以64位时间戳格式表示的上次设置或更正的本地时钟时间。

Original Timestamp 
这是以64位时间戳格式表示的请求离开客户端的时间。

Receive Timestamp 
这是以64位时间戳格式表示的请求到达服务器端的时间。

Transmit Timestamp 
这是以64位时间戳格式表示的应答离开服务器端的时间。

Authentication 
认证信息。


 NTP时间戳

 

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Integer Part                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Fraction Part                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            NTP时间戳格式

NTP时间戳使用的是自1970-01-01所经过的秒数(单位为秒),它分为整数部分和小数部分。NTP时间戳整数部分与ICMP时间戳消息所使用的时间戳格式整数部分一致,但小数部分却是不同的。

在64位的NTP时间戳中,前32位为整数部分,后32位为小数部分,其转换如下:

frac * 1e6 / (2<<32)

= frac / 4294.967296

根据以上转换规则可得NTP时间戳所能表示的最小精度为1 / 4294.967,296 = 0.2328307e-9,约等为0.232纳秒。

在32位的NTP时间戳中,前16位表示整数部分,后16位为小数部分,其转换与上面的类似: 

 frac * 1e6 / (2<<16)

= frac * 15.2587890625


NTP客户端操作 

关于往返时延和本地时钟偏移的计算 

为了计算相对于服务器的往返时延d和本地时钟偏移t,客户端根据客户端时钟设置请求中的发送时间戳。服务器将该字段复制到应答中的起始时间戳(Originate Timestamp),并根据服务器时钟设置接收时间戳(Receive Timestamp)和传送时间戳(Transmit Timestamp)。

当接收到服务器应答时,客户端根据NTP时间戳格式的时钟确定目的时间戳变量为到达时间。以下总结了四个时间戳:

Originate Timestamp     T1      客户端发送时间请求的时间
Receive Timestamp       T2      服务器收到时间请求的时间
Transmit Timestamp      T3      服务器发送时间回复的时间
Destination Timestamp   T4      客户端收到时间回复的时间

往返时延d和本地时钟偏移t定义为: 

d = (T4 - T1) - (T2 - T3)

t = ((T2 - T1) + (T3 - T4)) / 2


实现(C语言) 

为了方便对NTP报文进行操作,自定义了ntphdr结构体,如下:

ntphdr结构体定义比较特殊,由于按位定义了字段,所以需要解决字节序的问题,

可以使用宏__BYTE_ORDER(在头文件 中)来判断字节序。


/* 32位时间戳 */
struct s_fixedpt {
    uint16_t    intpart;
    uint16_t    fracpart;
};

/* 64位时间戳 */
struct l_fixedpt {
    uint32_t    intpart;
    uint32_t    fracpart;
};

struct ntphdr {
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int        ntp_li:2;
    unsigned int        ntp_vn:3;
    unsigned int        ntp_mode:3;
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int        ntp_mode:3;
    unsigned int        ntp_vn:3;
    unsigned int        ntp_li:2;
#endif
    uint8_t             ntp_stratum;
    uint8_t             ntp_poll;
    int8_t              ntp_precision;
    struct s_fixedpt    ntp_rtdelay;
    struct s_fixedpt    ntp_rtdispersion;
    uint32_t            ntp_refid;
    struct l_fixedpt    ntp_refts;
    struct l_fixedpt    ntp_orits;
    struct l_fixedpt    ntp_recvts;
    struct l_fixedpt    ntp_transts;
};

为了便于对NTP时间戳的操作,定义了以下宏:


/*
 *  自1900-01-01到1970-01-01所经过的秒数
 */
#define JAN_1970            0x83aa7e80


/*
 *  用于64位NTP时间戳,即小数部分为32位
 *      NTP_CONV_FRAC32(x)将x转换为NTP时间戳中的小数部分值;
 *      NTP_REVE_FRAC32(x)则相反,将NTP时间戳小数部分值x解析成具体值。
 */
#define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
#define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   


/*
 *  用于32位NTP时间戳,即小数部分为16位
 *      NTP_CONV_FRAC16(x)将x转换为NTP时间戳中的小数部分值;
 *      NTP_REVE_FRAC16(x)则相反,将NTP时间戳小数部分值x解析成具体值。
 */
#define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
#define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    


/*
 *  timeval结构中tv_usec字段和NTP时间戳小数部分互转
 */
#define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
#define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 


/*
 *  将l_fixedpt结构(NTP64位时间戳)换算成自1970-01-01所经过的秒数。
 *  该l_fixedpt结构的字段均为网络字节序。
 */
#define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   

函数主体:


int main(int argc, char *argv[])
{
    char buf[BUFSIZE];
    size_t nbytes;
    int sockfd, maxfd1;
    struct sockaddr_in servaddr;
    fd_set readfds;
    struct timeval timeout, recvtv, tv;
    double offset;

    if (argc != 2) 
    {
        usage();
        exit(-1);
    }


    //构建服务器地址套接字结构
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(NTP_PORT);
    servaddr.sin_addr.s_addr = inet_host(argv[1]);

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        perror("socket error");
        exit(-1);
    }

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) != 0) 
    {
        perror("connect error");
        exit(-1);
    }

    //构建并发送NTP请求报文
    nbytes = BUFSIZE;
    if (get_ntp_packet(buf, &nbytes) != 0) 
    {
        fprintf(stderr, "construct ntp request error \n");
        exit(-1);
    }
    send(sockfd, buf, nbytes, 0);


    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    maxfd1 = sockfd + 1;

    //设置select超时时间
    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;

    if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) 
    {
        if (FD_ISSET(sockfd, &readfds)) 
        {
            if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0) 
            {
                perror("recv error");
                exit(-1);
            }

            //计算客户端时间与服务器端时间偏移量
            gettimeofday(&recvtv, NULL);
            offset = get_offset((struct ntphdr *) buf, &recvtv);

            //更新系统时间
            gettimeofday(&tv, NULL);
            tv.tv_sec += (int) offset;
            tv.tv_usec += offset - (int) offset;

            if (settimeofday(&tv, NULL) != 0) 
            {
                perror("settimeofday error");
                exit(-1);
            }
            printf("%s \n", ctime((time_t *) &tv.tv_sec));
        }
    }

    //操作完成后别忘了关闭套接字
    close(sockfd);

    return 0;
}

NTP请求报文构建函数:


/*
 *  构建一个NTP请求报文
 *  参数:   buf指向存放NTP报文的缓冲区;
 *          size为值-结果参数,传入是为缓冲区长度,返回时为NTP报文长度
 */
int get_ntp_packet(void *buf, size_t *size)
{
    struct ntphdr *ntp;
    struct timeval tv;


    if (!size || *sizentp_li = NTP_LI;
    ntp->ntp_vn = NTP_VN;
    ntp->ntp_mode = NTP_MODE;
    ntp->ntp_stratum = NTP_STRATUM;
    ntp->ntp_poll = NTP_POLL;
    ntp->ntp_precision = NTP_PRECISION;

    gettimeofday(&tv, NULL);
    ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}

往返时延和时间偏移量计算函数:


/*
 *  获取客户端与服务器之间的往返时延
 *      参数ntp指向服务器应答所在的缓冲区;
 *      参数recvtv指向收到服务器应答的本地时间;
 */
double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return (t4 - t1) - (t3 - t2);
}


/*
 *  获取客户端与服务器的时间偏移量
 *      参数ntp指向服务器应答所在的缓冲区;
 *      参数recvtv指向收到服务器应答的本地时间;
 */
double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2;
}

完整源码:


/* ntpclient.c */
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define VERSION_3           3
#define VERSION_4           4

#define MODE_CLIENT         3
#define MODE_SERVER         4


#define NTP_LI              0
#define NTP_VN              VERSION_3   
#define NTP_MODE            MODE_CLIENT
#define NTP_STRATUM         0
#define NTP_POLL            4
#define NTP_PRECISION       -6

#define NTP_HLEN            48

#define NTP_PORT            123
#define NTP_SERVER          "182.92.12.11"

#define TIMEOUT             10

#define BUFSIZE             1500

#define JAN_1970            0x83aa7e80

#define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
#define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   

#define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
#define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    


#define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
#define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 


#define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   


struct s_fixedpt {
    uint16_t    intpart;
    uint16_t    fracpart;
};

struct l_fixedpt {
    uint32_t    intpart;
    uint32_t    fracpart;
};


struct ntphdr {
#if __BYTE_ORDER == __BID_ENDIAN
    unsigned int    ntp_li:2;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_mode:3;
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int    ntp_mode:3;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_li:2;
#endif
    uint8_t         ntp_stratum;
    uint8_t         ntp_poll;
    int8_t          ntp_precision;
    struct s_fixedpt    ntp_rtdelay;
    struct s_fixedpt    ntp_rtdispersion;
    uint32_t            ntp_refid;
    struct l_fixedpt    ntp_refts;
    struct l_fixedpt    ntp_orits;
    struct l_fixedpt    ntp_recvts;
    struct l_fixedpt    ntp_transts;
};


in_addr_t inet_host(const char *host)
{
    in_addr_t saddr;
    struct hostent *hostent;

    if ((saddr = inet_addr(host)) == INADDR_NONE) 
    {
        if ((hostent = gethostbyname(host)) == NULL)
            return INADDR_NONE;

        memmove(&saddr, hostent->h_addr, hostent->h_length);
    }

    return saddr;
}


int get_ntp_packet(void *buf, size_t *size)
{
    struct ntphdr *ntp;
    struct timeval tv;


    if (!size || *sizentp_li = NTP_LI;
    ntp->ntp_vn = NTP_VN;
    ntp->ntp_mode = NTP_MODE;
    ntp->ntp_stratum = NTP_STRATUM;
    ntp->ntp_poll = NTP_POLL;
    ntp->ntp_precision = NTP_PRECISION;

    gettimeofday(&tv, NULL);
    ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}


void print_ntp(struct ntphdr *ntp)
{
    time_t time;

    printf("LI:\t%d \n", ntp->ntp_li);
    printf("VN:\t%d \n", ntp->ntp_vn);
    printf("Mode:\t%d \n", ntp->ntp_mode);
    printf("Stratum:\t%d \n", ntp->ntp_stratum);
    printf("Poll:\t%d \n", ntp->ntp_poll);
    printf("precision:\t%d \n", ntp->ntp_precision);

    printf("Route delay:\t %lf \n",
        ntohs(ntp->ntp_rtdelay.intpart) + NTP_REVE_FRAC16(ntohs(ntp->ntp_rtdelay.fracpart)));
    printf("Route Dispersion:\t%lf \n",
        ntohs(ntp->ntp_rtdispersion.intpart) + NTP_REVE_FRAC16(ntohs(ntp->ntp_rtdispersion.fracpart)));
    printf("Referencd ID:\t %d \n", ntohl(ntp->ntp_refid));


    time = ntohl(ntp->ntp_refts.intpart) - JAN_1970;
    printf("Reference:\t%d %ld %s \n",
        ntohl(ntp->ntp_refts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_refts.fracpart)),
        ctime(&time));

    time = ntohl(ntp->ntp_orits.intpart) - JAN_1970;
    printf("Originate:\t%d %d frac=%ld (%s) \n",
        ntohl(ntp->ntp_orits.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_orits.fracpart)),
        ntohl(ntp->ntp_orits.fracpart),
        ctime(&time) );

    time = ntohl(ntp->ntp_recvts.intpart) - JAN_1970;
    printf("Receive:\t%d %d (%s) \n",
        ntohl(ntp->ntp_recvts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_recvts.fracpart)),
        ctime(&time) );

    time = ntohl(ntp->ntp_transts.intpart) - JAN_1970;
    printf("Transmit:\t%d %d (%s) \n",
        ntohl(ntp->ntp_transts.intpart) - JAN_1970,
        FRAC2USEC(ntohl(ntp->ntp_transts.fracpart)),
        ctime(&time) );
}


double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return (t4 - t1) - (t3 - t2);
}


double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2;
}


void usage(void)
{
    fprintf(stderr,
        "Usage : ntpclient"
        " destination"
        "\n"
    );
}


int main(int argc, char *argv[])
{
    char buf[BUFSIZE];
    size_t nbytes;
    int sockfd, maxfd1;
    struct sockaddr_in servaddr;
    fd_set readfds;
    struct timeval timeout, recvtv, tv;
    double offset;

    if (argc != 2) {
        usage();
        exit(-1);
    }


    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(NTP_PORT);
    servaddr.sin_addr.s_addr = inet_host(argv[1]);
    //servaddr.sin_addr.s_addr = inet_host("119.28.183.184");

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        perror("socket error");
        exit(-1);
    }

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) != 0) 
    {
        perror("connect error");
        exit(-1);
    }

    nbytes = BUFSIZE;
    if (get_ntp_packet(buf, &nbytes) != 0) 
    {
        fprintf(stderr, "construct ntp request error \n");
        exit(-1);
    }
    send(sockfd, buf, nbytes, 0);


    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    maxfd1 = sockfd + 1;

    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;

    if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) 
    {
        if (FD_ISSET(sockfd, &readfds)) 
        {
            if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0) 
            {
                perror("recv error");
                exit(-1);
            }
            //计算客户端时间与服务器端时间偏移量
            gettimeofday(&recvtv, NULL);
            offset = get_offset((struct ntphdr *) buf, &recvtv);
            //更新系统时间
            gettimeofday(&tv, NULL);
            tv.tv_sec += (int) offset;
            tv.tv_usec += offset - (int) offset;

            if (settimeofday(&tv, NULL) != 0) 
            {
                perror("settimeofday error");
                exit(-1);
            }
            printf("%s \n", ctime((time_t *) &tv.tv_sec));
        }
    }

    close(sockfd);

    return 0;
}

本代码作者总结:(因为整个都是抄录他人的,自己并未实际测试过)
            NTP时间戳是相对于1900-01-01的时间,且小数部分转换与一般时间戳转换不一样,具体为 frac * 1e6 / (2<

按位定义结构体的成员时需注意字节序问题,通过宏__BYTE_ORDER来判断(在头文件 中)。


3.用户甲参考修改:

1.从一个NTP服务器获取到时间

 选择的NTP服务器IP地址:119.28.183.184(百度可以查到国家授时中心IP等)

2.更新系统时间

 代码里的settimeofday(&tv, NULL)函数,是需要root权限的。怎么在普通用户下实现NTP同步呢,

             1).命令加程序:

                 先登录root用户设置程序的UID,#chmod u+s 文件名。

                 然后在更新系统时间部分添加如下代码:

//通过下面步骤则该用户不管在普通用户还是在root用户下都能获取root权限。
uid_t uid = getuid();
if (setuid(0)) {
    return -1;
}
//...
if (setuid(uid)) {   //恢复uid
}

             2).不使用命令的情况,完整代码如下

                   修改:

                              1).IP修改

                               2).settimeofday()函数替代处

#include 
#include 
#include 
#include 
#include
#include 
#include 
 #include 
#include 
#include 
#include 
#include 
#include 

#define VERSION_3           3
#define VERSION_4           4

#define MODE_CLIENT         3
#define MODE_SERVER         4
 
#define NTP_LI              0
#define NTP_VN              VERSION_3   
#define NTP_MODE            MODE_CLIENT
#define NTP_STRATUM         0
#define NTP_POLL            4
#define NTP_PRECISION       -6

#define NTP_HLEN            48

#define NTP_PORT            123
#define NTP_SERVER          "182.92.12.11"

#define TIMEOUT             10

#define BUFSIZE             1500

#define JAN_1970            0x83aa7e80

#define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
#define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   

#define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
#define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    


#define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
#define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 


#define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   

using namespace std;
struct s_fixedpt {
    uint16_t    intpart;
    uint16_t    fracpart;
};

struct l_fixedpt {
    uint32_t    intpart;
    uint32_t    fracpart;
};


struct ntphdr {
#if __BYTE_ORDER == __BID_ENDIAN
    unsigned int    ntp_li:2;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_mode:3;
#endif
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int    ntp_mode:3;
    unsigned int    ntp_vn:3;
    unsigned int    ntp_li:2;
#endif
    uint8_t         ntp_stratum;
    uint8_t         ntp_poll;
    int8_t          ntp_precision;
    struct s_fixedpt    ntp_rtdelay;
    struct s_fixedpt    ntp_rtdispersion;
    uint32_t            ntp_refid;
    struct l_fixedpt    ntp_refts;
    struct l_fixedpt    ntp_orits;
    struct l_fixedpt    ntp_recvts;
    struct l_fixedpt    ntp_transts;
};


in_addr_t inet_host(const char *host)
{
    in_addr_t saddr;
    struct hostent *hostent;

    if ((saddr = inet_addr(host)) == INADDR_NONE) {
        if ((hostent = gethostbyname(host)) == NULL)
            return INADDR_NONE;

        memmove(&saddr, hostent->h_addr, hostent->h_length);
    }

    return saddr;
}


int get_ntp_packet(void *buf, size_t *size)  //构建并发送NTP请求报文
{
    struct ntphdr *ntp;
    struct timeval tv;


    if (!size || *sizentp_li = NTP_LI;
    ntp->ntp_vn = NTP_VN;
    ntp->ntp_mode = NTP_MODE;
    ntp->ntp_stratum = NTP_STRATUM;
    ntp->ntp_poll = NTP_POLL;
    ntp->ntp_precision = NTP_PRECISION;

    gettimeofday(&tv, NULL);  //把目前的时间用tv 结构体返回
    ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}



double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)  //往返时延
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return (t4 - t1) - (t3 - t2);
}


double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)  //偏移量
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts);
    t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2;
}




int main(int argc, char *argv[])
{    
    char dateBuf[64] = {0};
    char cmd[128] = {0};
    tm* local; 
    char buf[BUFSIZE];
    size_t nbytes;
    int sockfd, maxfd1;
    struct sockaddr_in servaddr;
    fd_set readfds;
    struct timeval timeout, recvtv, tv;
    double offset;

    // if (argc != 2) {
        // usage();
        // exit(-1);
   

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(NTP_PORT);
    servaddr.sin_addr.s_addr = inet_host("119.28.183.184");
    
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket error");
        exit(-1);
    }

    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) != 0) {  
        perror("connect error");
        exit(-1);
    }
    nbytes = BUFSIZE;
    if (get_ntp_packet(buf, &nbytes) != 0) {
        fprintf(stderr, "construct ntp request error \n");
        exit(-1);
    }
    send(sockfd, buf, nbytes, 0);


    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    maxfd1 = sockfd + 1;


    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;
    
    if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) {
        if (FD_ISSET(sockfd, &readfds)) 
        {
            if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0)  
            {
                perror("recv error");
                exit(-1);
            }
            
            //计算C/S时间偏移量
            gettimeofday(&recvtv, NULL);
            offset = get_offset((struct ntphdr *) buf, &recvtv);    
            ////更新系统时间

            gettimeofday(&tv, NULL);
            
            tv.tv_sec += (int) offset +28800;
            tv.tv_usec += offset - (int) offset;
            
            local = localtime((time_t *) &tv.tv_sec); 
            strftime(dateBuf, 64, "%Y-%m-%d %H:%M:%S", local);
            sprintf(cmd, "system busybox date -s \"%s\"", dateBuf);
            
            printf("%s \n", ctime((time_t *) &tv.tv_sec));
            

        }
    }

    close(sockfd);

    return 0;
}

 

你可能感兴趣的:(学习)