我一直在尝试几个月来创build一个简单的基于RFC5905的 SNTP单客户机/服务器。 最后,我设法使它工作,至less我认为它工作正常,但是当我试图testing我的代码对一个真正的NTP服务器(例如0.se.pool.ntp.org:123)我收到的时间戳需要重新计算。 我已经尝试了几种不同的方法,但不pipe现在3天,但不pipe我什么都没做。
有谁知道如何将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
预期的输出将与我之前发布的类似。
提前感谢大家的时间和精力来帮助我。
更新相关的问题,但不是接近我正在寻找的答案如何编写一个NTP客户端? [closures] 。
将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时间戳是按照网络字节顺序排列的,因此您可能需要进行一些调整才能使用数字进行操作。
要从struct timeval
转换成unix时间的tv_usec
字段到uint64_t
并左移它的32位位置,然后将它除以1000000
并转换成网络字节顺序(最有意义的字节在前)
以下代码示例说明了这一点。
#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); }