linux C/C++實現同步NTP時間


搬磚萌新記錄工作點滴:


一、時間類型及常用函數

(1)時間類型

本地時間(locale time)
格林威治時間(Greenwich Mean Time GMT)
世界標准時間 (Universal Time Coordinated UTC)

 

GMT、UTC時間,都是以秒數為單位計數,而不是真實世界中的年月日,時分秒時間。

這個時間是從1970年01月01日 0:00:00起到現在經過的秒數例如運行下面代碼:

#include <time.h> #include <stdio.h>

int main() { time_t timep; //用來存儲從1970年到現在經過了多少秒 time(&timep); //獲取time_t類型的當前時間 printf("%ld\n", timep); 
    printf("%s", ctime(&timep)); 
return 0; }

得到:1539332642

通過函數ctime()將其轉換為真實世界時間:Fri Oct 12 16:30:01 2018

 

(2)常用時間函數舉例

因為時區不同的關系,不同函數取得的時間會相差8個小時(北京處於東八區)。簡單舉例:

獲得UTC時間:time()、asctime()、gmtime()... ...

獲得經時區轉換后的時間:ctime()、localtime()... ...

#include <time.h> #include <stdio.h>

int main() { time_t timep; time(&timep); printf("%ld\n", timep); printf("北京時間:%s", ctime(&timep)); printf("UTC時間:%s", asctime(gmtime(&timep))); return 0; } 

更多與時間相關的內容可參考這個博客:c++ 時間類型詳解(time_t和tm) 

 Linux C++中的時間函數(轉)

(3)UTC時間轉換成秒,再轉換成當前時間

#include <time.h>
#include <stdio.h>
/*
struct tm
{   int tm_sec; /* 秒 – 取值區間為[0,59] */   int tm_min; /* 分 - 取值區間為[0,59] */   int tm_hour; /* 時 - 取值區間為[0,23] */   int tm_mday; /* 一個月中的日期 - 取值區間為[1,31] */   int tm_mon; /* 月份(從一月開始,0代表一月) - 取值區間為[0,11] */   int tm_year; /* 年份,其值等於實際年份減去1900 */   int tm_wday; /* 星期 – 取值區間為[0,6],其中0代表星期天,1代表星期一 */   int tm_yday; /* 從每年1月1日開始的天數– 取值區間[0,365],其中0代表1月1日 */   int tm_isdst; /* 夏令時標識符,夏令時tm_isdst為正;不實行夏令時tm_isdst為0 */ };
*/
typedef struct RTSPUTCTime
{
    int year;
    int mon;
    int day;
    int hour;
    int min;
    int second;
} RTSPUTCTime;

time_t utc2seconds(RTSPUTCTime *utc)
{
    struct tm tm_time;

    memset(&tm_time, 0, sizeof(tm_time));
    tm_time.tm_year = utc->year - 1900;
    tm_time.tm_mon  = utc->mon;
    tm_time.tm_mday = utc->day;
    tm_time.tm_hour = utc->hour;
    tm_time.tm_min  = utc->min;
    tm_time.tm_sec  = utc->second;
return mktime(&tm_time);
}

int main()
{
  struct tm *tm_now;
  RTSPUTCTime utc = {2019,3,15,9,30,15};   // 給定一個UTC時間
  time_t seektime = utc2seconds(&utc);    // 將UTC時間轉化為秒
  tm_now = localtime(&seektime);        // 將秒轉化為當前時間

  printf("tm_now =%d-%d-%d %d:%d:%d \n",tm_now->tm_year+1900, tm_now->tm_mon, tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec);
}

 

 二、實現NTP同步功能

了解了時間概念后,要做的就比較明確了

(1)發送NTP請求報文,從一個NTP服務器獲取到時間

(2)更新系統時間 

 

這里可以參考博客:簡單的NTP客戶端-C語言實現,講解詳細,提供的代碼稍作修改編譯就通過了,很好用。

 

關於我修改的地方:

(1)從一個NTP服務器獲取到時間

我選擇的NTP服務器IP地址:119.28.183.184(百度可以查到國家授時中心IP等)

 

(2)更新系統時間

代碼里的settimeofday(&tv, NULL)函數,是需要root權限的。怎么在普通用戶下實現NTP同步呢,

①命令加程序:

先登錄root用戶設置程序的UID,#chmod u+s 文件名。

然后在更新系統時間部分添加如下代碼

uid_t uid = getuid(); if (setuid(0)) { return -1; } //...
if (setuid(uid)) {   //恢復uid
}

通過上面步驟則該用戶不管在普通用戶還是在root用戶下都能獲取root權限。

 

②不使用命令的情況,完整代碼如下

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <time.h>
 5 #include<iostream>
 6 #include <unistd.h>
 7 #include <sys/select.h>
 8  #include<sys/time.h> 
 9 #include <sys/socket.h>
 10 #include <arpa/inet.h>
 11 #include <netdb.h>
 12 #include <errno.h>
 13 #include <endian.h>
 14 
 15 #define VERSION_3           3
 16 #define VERSION_4           4
 17 
 18 #define MODE_CLIENT         3
 19 #define MODE_SERVER         4
 20  
 21 #define NTP_LI              0
 22 #define NTP_VN              VERSION_3   
 23 #define NTP_MODE            MODE_CLIENT
 24 #define NTP_STRATUM         0
 25 #define NTP_POLL            4
 26 #define NTP_PRECISION       -6
 27 
 28 #define NTP_HLEN            48
 29 
 30 #define NTP_PORT            123
 31 #define NTP_SERVER          "182.92.12.11"
 32 
 33 #define TIMEOUT             10
 34 
 35 #define BUFSIZE             1500
 36 
 37 #define JAN_1970            0x83aa7e80
 38 
 39 #define NTP_CONV_FRAC32(x)  (uint64_t) ((x) * ((uint64_t)1<<32))    
 40 #define NTP_REVE_FRAC32(x)  ((double) ((double) (x) / ((uint64_t)1<<32)))   
 41 
 42 #define NTP_CONV_FRAC16(x)  (uint32_t) ((x) * ((uint32_t)1<<16))    
 43 #define NTP_REVE_FRAC16(x)  ((double)((double) (x) / ((uint32_t)1<<16)))    
 44 
 45 
 46 #define USEC2FRAC(x)        ((uint32_t) NTP_CONV_FRAC32( (x) / 1000000.0 )) 
 47 #define FRAC2USEC(x)        ((uint32_t) NTP_REVE_FRAC32( (x) * 1000000.0 )) 
 48 
 49 
 50 #define NTP_LFIXED2DOUBLE(x)    ((double) ( ntohl(((struct l_fixedpt *) (x))->intpart) - JAN_1970 + FRAC2USEC(ntohl(((struct l_fixedpt *) (x))->fracpart)) / 1000000.0 ))   
 51 
 52 using namespace std;  53 struct s_fixedpt {  54  uint16_t intpart;  55  uint16_t fracpart;  56 };  57 
 58 struct l_fixedpt {  59  uint32_t intpart;  60  uint32_t fracpart;  61 };  62 
 63 
 64 struct ntphdr {  65 #if __BYTE_ORDER == __BID_ENDIAN
 66     unsigned int    ntp_li:2;  67     unsigned int    ntp_vn:3;  68     unsigned int    ntp_mode:3;  69 #endif
 70 #if __BYTE_ORDER == __LITTLE_ENDIAN
 71     unsigned int    ntp_mode:3;  72     unsigned int    ntp_vn:3;  73     unsigned int    ntp_li:2;  74 #endif
 75  uint8_t ntp_stratum;  76  uint8_t ntp_poll;  77  int8_t ntp_precision;  78     struct s_fixedpt ntp_rtdelay;  79     struct s_fixedpt ntp_rtdispersion;  80  uint32_t ntp_refid;  81     struct l_fixedpt ntp_refts;  82     struct l_fixedpt ntp_orits;  83     struct l_fixedpt ntp_recvts;  84     struct l_fixedpt ntp_transts;  85 };  86 
 87 
 88 in_addr_t inet_host(const char *host)  89 {  90  in_addr_t saddr;  91     struct hostent *hostent;  92 
 93     if ((saddr = inet_addr(host)) == INADDR_NONE) {  94         if ((hostent = gethostbyname(host)) == NULL)  95             return INADDR_NONE;  96 
 97         memmove(&saddr, hostent->h_addr, hostent->h_length);  98  }  99 
100     return saddr; 101 } 102 
103 
104 int get_ntp_packet(void *buf, size_t *size)  //構建並發送NTP請求報文
105 { 106     struct ntphdr *ntp; 107     struct timeval tv; 108 
109 
110     if (!size || *size<NTP_HLEN) 111         return -1; 112 
113     memset(buf, 0, *size); 114 
115     ntp = (struct ntphdr *) buf; 116     ntp->ntp_li = NTP_LI; 117     ntp->ntp_vn = NTP_VN; 118     ntp->ntp_mode = NTP_MODE; 119     ntp->ntp_stratum = NTP_STRATUM; 120     ntp->ntp_poll = NTP_POLL; 121     ntp->ntp_precision = NTP_PRECISION; 122 
123     gettimeofday(&tv, NULL);  //把目前的時間用tv 結構體返回
124     ntp->ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970); 125     ntp->ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec)); 126 
127     *size = NTP_HLEN; 128 
129     return 0; 130 } 131 
132 
133 
134 double get_rrt(const struct ntphdr *ntp, const struct timeval *recvtv)  //往返時延
135 { 136     double t1, t2, t3, t4; 137 
138     t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits); 139     t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts); 140     t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts); 141     t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0; 142 
143     return (t4 - t1) - (t3 - t2); 144 } 145 
146 
147 double get_offset(const struct ntphdr *ntp, const struct timeval *recvtv)  //偏移量
148 { 149     double t1, t2, t3, t4; 150 
151     t1 = NTP_LFIXED2DOUBLE(&ntp->ntp_orits); 152     t2 = NTP_LFIXED2DOUBLE(&ntp->ntp_recvts); 153     t3 = NTP_LFIXED2DOUBLE(&ntp->ntp_transts); 154     t4 = recvtv->tv_sec + recvtv->tv_usec / 1000000.0; 155 
156     return ((t2 - t1) + (t3 - t4)) / 2; 157 } 158 
159 
160 
161 
162 int main(int argc, char *argv[]) 163 { 164     char dateBuf[64] = {0}; 165     char cmd[128] = {0}; 166     tm* local; 167     char buf[BUFSIZE]; 168  size_t nbytes; 169     int sockfd, maxfd1; 170     struct sockaddr_in servaddr; 171  fd_set readfds; 172     struct timeval timeout, recvtv, tv; 173     double offset; 174 
175     // if (argc != 2) { 176         // usage(); 177         // exit(-1); 178    
179 
180     servaddr.sin_family = AF_INET; 181     servaddr.sin_port = htons(NTP_PORT); 182     servaddr.sin_addr.s_addr = inet_host("119.28.183.184"); 183     
184     if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 185         perror("socket error"); 186         exit(-1); 187  } 188 
189     if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) != 0) { 190         perror("connect error"); 191         exit(-1); 192  } 193     nbytes = BUFSIZE; 194     if (get_ntp_packet(buf, &nbytes) != 0) { 195         fprintf(stderr, "construct ntp request error \n"); 196         exit(-1); 197  } 198     send(sockfd, buf, nbytes, 0); 199 
200 
201     FD_ZERO(&readfds); 202     FD_SET(sockfd, &readfds); 203     maxfd1 = sockfd + 1; 204 
205 
206     timeout.tv_sec = TIMEOUT; 207     timeout.tv_usec = 0; 208     
209     if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) { 210         if (FD_ISSET(sockfd, &readfds)) 211  { 212             if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0) 213  { 214                 perror("recv error"); 215                 exit(-1); 216  } 217             
218             //計算C/S時間偏移量
219             gettimeofday(&recvtv, NULL); 220             offset = get_offset((struct ntphdr *) buf, &recvtv); 221             ////更新系統時間
222 
223             gettimeofday(&tv, NULL); 224             
225             tv.tv_sec += (int) offset +28800; 226             tv.tv_usec += offset - (int) offset; 227             
228             local = localtime((time_t *) &tv.tv_sec); 229             strftime(dateBuf, 64, "%Y-%m-%d %H:%M:%S", local); 230             sprintf(cmd, "system busybox date -s \"%s\"", dateBuf); 231             
232             printf("%s \n", ctime((time_t *) &tv.tv_sec)); 233             
234 
235  } 236  } 237 
238  close(sockfd); 239 
240     return 0; 241 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM