如何在 C 语言 (Linux) 中将 NTP 时间转换为 Unix 纪元时间
Posted
技术标签:
【中文标题】如何在 C 语言 (Linux) 中将 NTP 时间转换为 Unix 纪元时间【英文标题】:How to convert NTP time to Unix Epoch time in C language (Linux) 【发布时间】:2015-05-20 15:32:50 【问题描述】:几个月来,我一直在尝试基于RFC5905 创建一个简单的 SNTP 单一客户端/服务器。最后我设法让它工作,至少我认为它工作正常,但是当我尝试针对真正的 NTP 服务器(例如 0.se.pool.ntp.org:123)测试我的代码时,我收到的时间戳需要被重新计算。我尝试了几种不同的方法,但现在已经 3 天了,但无论如何我都没有尝试过。
有人知道如何将 NTP 时间戳转换为 Unix 纪元时间戳吗?
执行服务器的语法,例如./server 127.0.0.1:5000 和客户端例如./client 127.0.0.1:5000
针对真实 NTP 服务器执行客户端的语法,例如./client 0.se.pool.ntp.org:123
工作代码客户端示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>
#include <assert.h>
#define UNIX_EPOCH 2208988800UL /* 1970 - 1900 in seconds */
typedef struct client_packet client_packet;
struct client_packet
uint8_t client_li_vn_mode;
uint8_t client_stratum;
uint8_t client_poll;
uint8_t client_precision;
uint32_t client_root_delay;
uint32_t client_root_dispersion;
uint32_t client_reference_identifier;
uint32_t client_reference_timestamp_sec;
uint32_t client_reference_timestamp_microsec;
uint32_t client_originate_timestamp_sec;
uint32_t client_originate_timestamp_microsec;
uint32_t client_receive_timestamp_sec;
uint32_t client_receive_timestamp_microsec;
uint32_t client_transmit_timestamp_sec;
uint32_t client_transmit_timestamp_microsec;
__attribute__((packed));
typedef struct server_send server_send;
struct server_send
uint8_t server_li_vn_mode;
uint8_t server_stratum;
uint8_t server_poll;
uint8_t server_precision;
uint32_t server_root_delay;
uint32_t server_root_dispersion;
char server_reference_identifier[4];
uint32_t server_reference_timestamp_sec;
uint32_t server_reference_timestamp_microsec;
uint32_t server_originate_timestamp_sec;
uint32_t server_originate_timestamp_microsec;
uint32_t server_receive_timestamp_sec;
uint32_t server_receive_timestamp_microsec;
uint32_t server_transmit_timestamp_sec;
uint32_t server_transmit_timestamp_microsec;
__attribute__((packed));
/* Linux man page bind() */
#define handle_error(msg) \
do perror(msg); exit(EXIT_FAILURE); while (0)
uint32_t ClockGetTime()
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
int main(int argc, char *argv[])
int sockfd , numbytes;
struct addrinfo hints, *servinfo, *p;
int rv;
client_packet memsend;
server_send memrcv;
memset( &memsend , 0 , sizeof memsend );
memset( &memrcv , 0 , sizeof memrcv );
char IP[16]; /* IP = 15 digits 1 extra for \0 null terminating character string */
char PORT_STR[6]; /* Port = 5 digits MAX 1 extra for \0 null terminating character string */
memset(IP , '\0' , sizeof(IP));
memset(PORT_STR , '\0' , sizeof(PORT_STR));
strcpy(IP, strtok(argv[1], ":"));
strcpy(PORT_STR, strtok(NULL, ":"));
memset( &hints , 0 , sizeof hints );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ( ( rv = getaddrinfo( IP , PORT_STR , &hints , &servinfo ) ) != 0 )
fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(rv) );
return 1;
// loop through all the results and make a socket
for( p = servinfo; p != NULL; p = p->ai_next )
if ( ( sockfd = socket( p->ai_family , p->ai_socktype , p->ai_protocol ) ) == -1 )
handle_error( "socket" );
continue;
break;
if (p == NULL)
fprintf(stderr, "Error while binding socket\n");
return 2;
memsend.client_li_vn_mode = 0b00100011;
memsend.client_stratum = 0;
memsend.client_poll = 0;
memsend.client_precision = 0;
memsend.client_root_delay = 0;
memsend.client_root_dispersion = 0;
memsend.client_reference_identifier = 0;
memsend.client_reference_timestamp_sec = 0;
memsend.client_reference_timestamp_microsec = 0;
memsend.client_receive_timestamp_sec = 0;
memsend.client_receive_timestamp_microsec = 0;
time_t time_originate_sec = time(NULL);
memsend.client_originate_timestamp_sec = time_originate_sec;
memsend.client_originate_timestamp_microsec = ClockGetTime();
memsend.client_transmit_timestamp_sec = memsend.client_originate_timestamp_sec;
memsend.client_transmit_timestamp_microsec = memsend.client_originate_timestamp_microsec;
if ( ( numbytes = sendto( sockfd, &memsend , sizeof memsend , 0 ,
p->ai_addr , p->ai_addrlen ) ) == -1 )
handle_error("sendto");
exit(1);
if ( ( numbytes = recvfrom( sockfd , &memrcv , sizeof memrcv , 0 ,
(struct sockaddr *) &p->ai_addr, &p->ai_addrlen ) ) == -1 )
handle_error( "recvfrom" );
exit(1);
time_t time_rcv_sec = time(NULL);
uint32_t client_rcv_timestamp_sec = time_rcv_sec;
uint32_t client_rcv_timestamp_microsec = ClockGetTime();
freeaddrinfo(servinfo);
char Identifier[5];
memset(Identifier , '\0' , sizeof Identifier);
memcpy(Identifier , memrcv.server_reference_identifier , sizeof memrcv.server_reference_identifier);
printf("\t Reference Identifier \t %"PRIu32" \t\t\t %s\n",memsend.client_reference_identifier,Identifier);
printf("\t Reference Timestamp \t %"PRIu32".%"PRIu32" \t\t\t %"PRIu32".%"PRIu32"\n",memsend.client_reference_timestamp_sec,memsend.client_reference_timestamp_microsec,memrcv.server_reference_timestamp_sec,memrcv.server_reference_timestamp_microsec);
printf("\t Originate Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",memsend.client_originate_timestamp_sec,memsend.client_originate_timestamp_microsec,memrcv.server_originate_timestamp_sec,memrcv.server_originate_timestamp_microsec);
printf("\t Receive Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",client_rcv_timestamp_sec,client_rcv_timestamp_microsec,memrcv.server_receive_timestamp_sec,memrcv.server_receive_timestamp_microsec);
printf("\t Transmit Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n\n",memsend.client_transmit_timestamp_sec,memsend.client_transmit_timestamp_microsec,memrcv.server_transmit_timestamp_sec,memrcv.server_transmit_timestamp_microsec);
close(sockfd);
return 0;
服务器代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>
#define TRUE 1
typedef struct client_send client_send;
struct client_send
uint8_t client_li_vn_mode;
uint8_t client_startum;
uint8_t client_poll;
uint8_t client_precision;
uint32_t client_root_delay;
uint32_t client_root_dispersion;
uint32_t client_reference_identifier;
uint32_t client_reference_timestamp_sec;
uint32_t client_reference_timestamp_microsec;
uint32_t client_originate_timestamp_sec;
uint32_t client_originate_timestamp_microsec;
uint32_t client_receive_timestamp_sec;
uint32_t client_receive_timestamp_microsec;
uint32_t client_transmit_timestamp_sec;
uint32_t client_transmit_timestamp_microsec;
__attribute__((packed));
typedef struct server_packet server_packet;
struct server_packet
uint8_t server_li_vn_mode;
uint8_t server_startum;
uint8_t server_poll;
uint8_t server_precision;
uint32_t server_root_delay;
uint32_t server_root_dispersion;
char server_reference_identifier[4];
uint32_t server_reference_timestamp_sec;
uint32_t server_reference_timestamp_microsec;
uint32_t server_originate_timestamp_sec;
uint32_t server_originate_timestamp_microsec;
uint32_t server_receive_timestamp_sec;
uint32_t server_receive_timestamp_microsec;
uint32_t server_transmit_timestamp_sec;
uint32_t server_transmit_timestamp_microsec;
__attribute__((packed));
/* Linux man page bind() */
#define handle_error(msg) \
do perror(msg); exit(EXIT_FAILURE); while (0)
uint32_t ClockGetTime()
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
unsigned long int precision()
struct timespec res;
if ( clock_getres( CLOCK_REALTIME, &res) == -1 )
perror( "clock get resolution" );
return EXIT_FAILURE;
return res.tv_nsec / 1000;
void *get_in_addr(struct sockaddr *sa)
if (sa->sa_family == AF_INET)
return &(((struct sockaddr_in*)sa)->sin_addr);
return &(((struct sockaddr_in6*)sa)->sin6_addr);
int main(int argc, char *argv[])
server_packet send_mem;
client_send rcv_mem;
/* Empty structs */
memset( &send_mem , 0 , sizeof send_mem );
memset( &rcv_mem , 0 , sizeof rcv_mem );
char s[INET_ADDRSTRLEN];
struct addrinfo hints, *servinfo, *p;
struct sockaddr_storage their_addr;
socklen_t addr_len;
int get, numbytes;
int sockfd;
char IP[16];
char PORT_STR[6];
memset(IP , '\0' , sizeof(IP));
memset(PORT_STR , '\0' , sizeof(PORT_STR));
strcpy(IP, strtok(argv[1], ":"));
strcpy(PORT_STR, strtok(NULL, ":"));
memset( &hints , 0 , sizeof hints );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = IPPROTO_UDP;
if ( ( get = getaddrinfo( NULL , PORT_STR , &hints , &servinfo ) ) != 0)
fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(get) );
return 1;
for( p = servinfo; p != NULL; p = p->ai_next )
if ( ( sockfd = socket( p->ai_family , p->ai_socktype ,
p->ai_protocol ) ) == -1 )
handle_error("socket");
continue;
if ( bind( sockfd , p->ai_addr , p->ai_addrlen ) == -1 )
close(sockfd);
handle_error("bind");
continue;
break;
if (p == NULL)
fprintf(stderr, "Not able to bind socket\n");
return 2;
freeaddrinfo(servinfo);
printf("\nServer is up and running: waiting to recv msg at port: %s...\n",
PORT_STR);
while(TRUE)
time_t t_ref_sec = time(NULL);
unsigned long int Ref_epoc_sec = t_ref_sec;
send_mem.server_reference_timestamp_sec = Ref_epoc_sec;
unsigned long int t_ref_nanosec = ClockGetTime();
send_mem.server_reference_timestamp_microsec = t_ref_nanosec;
addr_len = sizeof(their_addr);
if ((numbytes = recvfrom(sockfd, &rcv_mem , sizeof rcv_mem , 0,
(struct sockaddr *)&their_addr, &addr_len)) == -1)
handle_error("recvfrom");
exit(1);
time_t t_rcv_sec = time(NULL);
send_mem.server_receive_timestamp_sec = t_rcv_sec;
send_mem.server_receive_timestamp_microsec = ClockGetTime();
printf("Peer address: %s\n",
inet_ntop(their_addr.ss_family,
get_in_addr((struct sockaddr *)&their_addr),
s, sizeof(s)));
printf("Peer port: %i\n",p->ai_socktype);
send_mem.server_li_vn_mode = 0b00100100;
send_mem.server_startum = 0b00000001;
send_mem.server_poll = 0b00000110;
send_mem.server_precision = precision();
send_mem.server_root_delay = 0;
send_mem.server_root_dispersion = 0;
memcpy( send_mem.server_reference_identifier , "LOCL" ,
sizeof send_mem.server_reference_identifier );
send_mem.server_originate_timestamp_sec = rcv_mem.client_originate_timestamp_sec;
send_mem.server_originate_timestamp_microsec = rcv_mem.client_originate_timestamp_microsec;
time_t t_send_sec = time(NULL);
send_mem.server_transmit_timestamp_sec = t_send_sec;
send_mem.server_transmit_timestamp_microsec = ClockGetTime();
if ( sendto( sockfd, &send_mem , sizeof send_mem , 0 ,
(struct sockaddr *) &their_addr , addr_len ) == -1 )
handle_error("sendto");
exit(1);
close(sockfd);
return 0;
使用服务器和客户端时的打印输出示例。
Reference Identifier 0 LOCL
Reference Timestamp 0.0 1426637081.3564398733
Originate Timestamp 1426637087.3570333925 1426637087.3570333925
Receive Timestamp 1426637087.3570334078 1426637087.3570334003
Transmit Timestamp 1426637087.3570333925 1426637087.3570334046
当我探测真实的 NTP 服务器时的打印输出示例(例如 0.se.pool.ntp.org:123)。
Reference Identifier 0 �$�
Reference Timestamp 0.0 3879449560.3503094062
Originate Timestamp 1426637090.3573978972 1426637090.3573978972
Receive Timestamp 1426637090.3573992772 2722083800.781009125
Transmit Timestamp 1426637090.3573978972 2722083800.937312997
预期的输出将类似于我之前发布的打印输出。
提前感谢大家花时间和精力帮助我。
更新相关问题,但与我正在寻找的答案不接近How to write a NTP client? [closed]。
【问题讨论】:
NTP 使用 1900-01-01 00:00:00 的纪元,因此偏移量应为 2208988800 秒。闰秒可能是个问题。 en.wikipedia.org/wiki/Network_Time_Protocol#Timestamps 您好@KeithThompson,感谢您的时间和精力。我还尝试添加闰秒,并在减去 2208988800 秒之后,但时间仍然远未结束。但再次感谢您的建议。 当前NTP时间戳为3635628531秒,超过2**31-1。尝试使用int64_t
进行算术运算。
@KeithThompson 我也为计算部分做了int64_t T1 = memsend.client_transmit_timestamp_sec; T1 = T1 * 1000000LL /* microseconds */ + memsend.client_transmit_timestamp_microsec;
,但数字仍然不正确。我没有在这里发布我的代码的这些部分,以避免人们对不相关的代码发表负面评论。再次感谢您的时间和精力。
将 NTP 时间戳转换为 unix 时间戳时不考虑闰秒,因为这两个时间都是 UTC 时间。 UTC 时间是以秒为单位的连续单调刻度。从原子时间更改为 UTC 或反之亦然时会考虑闰秒。
【参考方案1】:
将 NTP 时间戳转换为 Unix 时间戳(struct timeval)涉及两个需要解决的问题。
一个是两个时期之间的偏移量。 Unix 使用位于 1/1/1970-00:00h (UTC) 的纪元,而 NTP 使用 1/1/1900-00:00h。这导致偏移量相当于 70 年的秒数(两个日期之间有 17 个闰年,所以偏移量是
(70*365 + 17)*86400 = 2208988800
从 NTP 时间中减去得到 Unix struct timeval
。
第二个是struct timeval
使用1/1000000 sec
作为亚秒分数单位,NTP 使用1/2^32 sec
作为其分数时间单位。要将 NTP 转换为struct timeval
,可以将小数部分除以2^32
(这很简单,它是右移),然后乘以1000000
。为了解决这个问题,我们必须使用 64 位算法,因为数字范围在 0
和 2^32
之间,所以最好是:
从 NTP 转换为 struct timeval
将小数部分字段(NTP 时间戳的右 32 位)复制到 uint64_t
变量并乘以 1000000
,然后将其右移 32 位位置以获得正确的值。您必须考虑到 NTP 时间戳按网络字节顺序排列,因此您可能需要进行一些调整才能使用这些数字。
以下代码示例说明了这一点。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <getopt.h>
#define OFFSET 2208988800ULL
void ntp2tv(uint8_t ntp[8], struct timeval *tv)
uint64_t aux = 0;
uint8_t *p = ntp;
int i;
/* we get the ntp in network byte order, so we must
* convert it to host byte order. */
for (i = 0; i < sizeof ntp / 2; i++)
aux <<= 8;
aux |= *p++;
/* for */
/* now we have in aux the NTP seconds offset */
aux -= OFFSET;
tv->tv_sec = aux;
/* let's go with the fraction of second */
aux = 0;
for (; i < sizeof ntp; i++)
aux <<= 8;
aux |= *p++;
/* for */
/* now we have in aux the NTP fraction (0..2^32-1) */
aux *= 1000000; /* multiply by 1e6 */
aux >>= 32; /* and divide by 2^32 */
tv->tv_usec = aux;
/* ntp2tv */
void tv2ntp(struct timeval *tv, uint8_t ntp[8])
uint64_t aux = 0;
uint8_t *p = ntp + sizeof ntp;
int i;
aux = tv->tv_usec;
aux <<= 32;
aux /= 1000000;
/* we set the ntp in network byte order */
for (i = 0; i < sizeof ntp/2; i++)
*--p = aux & 0xff;
aux >>= 8;
/* for */
aux = tv->tv_sec;
aux += OFFSET;
/* let's go with the fraction of second */
for (; i < sizeof ntp; i++)
*--p = aux & 0xff;
aux >>= 8;
/* for */
/* ntp2tv */
size_t print_tv(struct timeval *t)
return printf("%ld.%06ld\n", t->tv_sec, t->tv_usec);
size_t print_ntp(uint8_t ntp[8])
int i;
int res = 0;
for (i = 0; i < sizeof ntp; i++)
if (i == sizeof ntp / 2)
res += printf(".");
res += printf("%02x", ntp[i]);
/* for */
res += printf("\n");
return res;
/* print_ntp */
int main(int argc, char *argv[])
struct timeval t;
uint8_t ntp[8];
gettimeofday(&t, NULL);
printf("tv2ntp\n");
tv2ntp(&t, ntp);
printf("tv : "); print_tv(&t);
printf("ntp: "); print_ntp(ntp);
printf("ntp2tv\n");
ntp2tv(ntp, &t);
printf("tv : "); print_tv(&t);
printf("ntp: "); print_ntp(ntp);
【讨论】:
你是绝对正确的,为了完成我对我的问题的回答,你还需要将 int 从网络字节顺序转换为字节顺序。这可以在 htons()、htonl()、ntohs()、ntohl() 的帮助下完成。再次感谢您的时间和精力。 @Thanos,它正在被转换,但不使用 htons()、htonl() 或 htohs() 函数。它在 for(;;) 循环中实现。您可以独立于架构的字节序来验证它的真实性。试试看。 你好@LuisColorado,我看到你已经手动实现了。我只是把它作为评论,因为我昨晚在尝试解决它时发现了这个功能。再次感谢您花时间和精力回答我的问题。作为一个初学者,我有很多东西要学。一旦我有时间使用它,我会立即用答案更新问题。【参考方案2】:接受的答案相当陈旧,不再在 GCC 9.3.0 上编译,但它激发了我编写 C++ 等价物的灵感,多亏了 chrono 库,转换变得非常简单:
#include <chrono>
#include <cstddef>
#include <iomanip>
#include <iostream>
using namespace std::chrono;
using namespace std::chrono_literals;
using fractions = duration<std::int64_t, std::ratio<1, 0x100000000>>; // 1/(2^32)
using ntp_t = std::uint64_t;
auto tp2ntp(system_clock::time_point tp)
// "shift" epoch from unix 1/1/1970 to ntp 1/1/1900 (70 years + 17 leap days)
tp += (70 * 365 + 17) * 24h;
auto total = tp.time_since_epoch();
auto secs = duration_cast<seconds>(total);
auto fracs = duration_cast<fractions>(total - secs);
return static_cast<ntp_t>( (secs.count() << 32) | fracs.count() );
auto ntp2tp(ntp_t ntp)
auto tp = system_clock::time_point(); // epoch
// "shift" epoch from ntp 1/1/1900 to unix 1/1/1970 (70 years + 17 leap days)
tp -= (70 * 365 + 17) * 24h;
tp += seconds(ntp >> 32);
tp += duration_cast<system_clock::duration>( fractions(ntp & 0xffffffff) );
return tp;
void print_tp(system_clock::time_point tp)
auto total = tp.time_since_epoch();
auto secs = duration_cast<seconds>(total);
auto usecs = duration_cast<microseconds>(total - secs);
std::cout << "tp : " << secs.count() << '.' << usecs.count() << std::endl;
void print_ntp(ntp_t ntp)
std::cout << "ntp: " << std::hex << (ntp >> 32) << '.' << (ntp & 0xffffffff) << std::dec << std::endl;
int main(int argc, char *argv[])
auto tp = system_clock::now();
std::cout << "tp2ntp" << std::endl;
auto ntp = tp2ntp(tp);
print_tp(tp);
print_ntp(ntp);
std::cout << "ntp2tp" << std::endl;
auto tp2 = ntp2tp(ntp);
print_tp(tp2);
print_ntp(ntp);
return 0;
【讨论】:
谢谢。这很有帮助。但是,我希望您能澄清一个方面:对于 tp2ntp(),比率不应该是 吗?简单来说,我们不是还需要将小数部分除以 1000000 吗?可能类似的评论也适用于 ntp2tp() 。但现在,我的重点是 tp2ntp()。 @venk 不确定我是否理解您的问题,但这就是它的工作原理。我们接收tp
并将其时代转移到 1/1/1900。然后计算total
中的刻度数。刻度可以是微秒、毫秒或其他任何值(取决于系统)。它们的长度(又名句点)是total
的类签名的一部分。我们调用duration_cast
并要求它将它们转换为1 秒长的滴答并将它们存储在secs
中。剩余的刻度 (total - secs
) 被转换为 1/(2^32) 长刻度(即fractions
)并存储在fracs
中。有意义吗?
在 C++20 中,年和日有了新的文字,所以 (70 * 365 + 17) * 24h
可以缩短为 70y + 17d
。以上是关于如何在 C 语言 (Linux) 中将 NTP 时间转换为 Unix 纪元时间的主要内容,如果未能解决你的问题,请参考以下文章
NTP校时,网络校时,NVR校时,录像机NTP校时,京准电子