如何使用C语言将NTP时间转换为Unix Epoch时间(Linux) [英] How to convert NTP time to Unix Epoch time in C language (Linux)

查看:291
本文介绍了如何使用C语言将NTP时间转换为Unix Epoch时间(Linux)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

几个月以来,我一直在尝试基于 RFC5905 .最终,我设法使其至少能够正常工作,但是当我尝试针对真实的NTP服务器(例如0.se.pool.ntp.org:123)测试代码时,我需要接收的时间戳重新计算.我已经尝试了几种不同的方法,但是现在已经3天了,但是无论我什么都没有尝试.

I have been trying for several months to create a simple SNTP single Client/Server based on RFC5905. Finally I manage to make it work at least I think it works correctly, but when I tried to test my code against a real NTP server (e.g. 0.se.pool.ntp.org:123) the timestamps that I am receiving need to be recalculated. I have tried several different approaches but no matter for 3 days now but no matter what I tried nothing yet.

有人知道如何将NTP时间戳转换为Unix纪元时间戳吗?

Does anybody know how to convert the NTP timestamp to Unix epoch timestamp?

执行服务器的语法,例如./服务器127.0.0.1:5000和客户端,例如./client 127.0.0.1:5000

Syntax to execute the Server e.g. ./server 127.0.0.1:5000 and Client e.g. ./client 127.0.0.1:5000

针对实际NTP服务器执行客户端的语法,例如./client 0.se.pool.ntp.org:123

Syntax to execute the Client against a real NTP server e.g. ./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;
}

使用服务器和客户端时的打印输出示例.

Sample of printed output when I use Server and Client.

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).

Sample of printed output when I am probing a real NTP server (e.g. 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

预期输出将类似于我之前发布的打印输出.

The expected output would be something similar to print out as I posted before.

提前感谢大家的时间和精力为我提供帮助.

Thank you in advance for everyones time and effort to assist me.

更新相关问题,但与我正在寻找的答案不尽相同

Update Relevant question but not close to the answer that I am looking for How to write a NTP client? [closed].

推荐答案

将NTP时间戳转换为Unix时间戳(结构timeval)涉及两个要解决的问题.

converting NTP timestamps to Unix timestamps (struct timeval) involves two problems to be solved.

一个是两个历元之间的偏移量. Unix使用位于1/1/1970-00:00h(UTC)的时期,而NTP使用1/1/1900-00:00h.这将导致相当于70年的偏移量(以秒为单位)(两个日期之间有17个leap年,因此偏移量为

One is the offset between the two epochs. Unix uses an epoch located at 1/1/1970-00:00h (UTC) and NTP uses 1/1/1900-00:00h. This leads to an offset equivalent to 70 years in seconds (there are 17 leap years between the two dates so the offset is

(70*365 + 17)*86400 = 2208988800

从NTP时间中减去以获得Unix struct timeval.

to be substracted from NTP time to get Unix struct timeval.

第二个原因是struct timeval使用1/1000000 sec作为亚秒分数的单位,而NTP使用1/2^32 sec作为其分数时间的单位.要从NTP转换为struct timeval,可以将小数部分除以2^32(这很简单,这是一个右移),然后乘以1000000.为了解决这个问题,我们必须使用64位算术,因为数字范围在02^32之间,所以最好是:

The second is that struct timeval uses 1/1000000 sec as unit of subsecond fractions and NTP uses 1/2^32 sec as its unit of fractional time. To convert from NTP to struct timeval one might divide the fractional part by 2^32 (this is easy, it's a right shift) and then multiply by 1000000. To cope with this, we have to use 64 bit arithmetic, as numbers range between 0 and 2^32 so, the best is:

  • 从NTP转换为struct timeval将小数部分字段(NTP时间戳的右32位)复制到uint64_t变量,然后乘以1000000,然后右移32位位置以获取适当的值.您必须考虑到NTP时间戳是以网络字节顺序排列的,因此也许您必须进行一些调整才能使用数字.

  • to convert from NTP to struct timeval copy the fractional part field (the right 32 bits of a NTP timestamp) to a uint64_t variable and multiply it by 1000000, then right shift it by 32 bit positions to get the proper value. You must take into account that NTP timestamps are in network byte order, so perhaps you'll have to make some adjustments to be able to operate with the numbers.

要从struct timeval进行转换,请将unix时间的tv_usec字段复制到uint64_t并将其左移32位位置,然后将其除以1000000并转换为网络字节顺序(大多数有意义的字节在前)

To convert from struct timeval copy the tv_usec field of the unix time to a uint64_t and left shift it 32 bit positions, then divide it by 1000000 and convert to network byte order (most significative byte first)

以下代码示例对此进行了说明.

The following code sample illustrates this.

#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);
}

这篇关于如何使用C语言将NTP时间转换为Unix Epoch时间(Linux)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆