我尝试了几个月来基于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
预期输出应该类似于我之前发布的打印输出。
感谢大家抽出时间和努力来帮助我。
更新:相关问题,但不接近我正在寻找的答案 如何编写NTP客户端?[已关闭]。
int64_t
进行算术运算。 - Keith Thompsonhtonl()主机到网络长整型
将我发送的数字进行转换,也没有使用ntohl()网络到主机长整型
接收到的数字进行转换。这可能会影响我的服务器响应,我会应用它并发布我的结果。再次感谢您的时间和努力。 - Thanos