简体   繁体   English

如何在 C 语言 (Linux) 中将 NTP 时间转换为 Unix Epoch 时间

[英]How to convert NTP time to Unix Epoch time in C language (Linux)

I have been trying for several months to create a simple SNTP single Client/Server based on RFC5905 .几个月来,我一直在尝试基于RFC5905创建一个简单的 SNTP 单客户端/服务器。 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 (eg 0.se.pool.ntp.org:123) the timestamps that I am receiving need to be recalculated.最后我设法让它工作至少我认为它工作正常,但是当我尝试针对真正的 NTP 服务器(例如 0.se.pool.ntp.org:123)测试我的代码时,我收到的时间戳需要重新计算。 I have tried several different approaches but no matter for 3 days now but no matter what I tried nothing yet.我已经尝试了几种不同的方法,但现在已经 3 天了,但无论如何我都没有尝试过。

Does anybody know how to convert the NTP timestamp to Unix epoch timestamp?有人知道如何将 NTP 时间戳转换为 Unix 纪元时间戳吗?

Syntax to execute the Server eg ./server 127.0.0.1:5000 and Client eg ./client 127.0.0.1:5000执行 Server eg ./server 127.0.0.1:5000 和 Client eg ./client 127.0.0.1:5000 的语法

Syntax to execute the Client against a real NTP server eg ./client 0.se.pool.ntp.org:123针对真实 NTP 服务器执行客户端的语法,例如 ./client 0.se.pool.ntp.org:123

Sample of working code Client:工作代码客户端示例:

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

Sample of Server code:服务器代码示例:

#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

Sample of printed output when I am probing a real NTP server (eg 0.se.pool.ntp.org:123).当我探测真正的 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

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?更新相关问题但不接近我正在寻找的答案如何编写 NTP 客户端? [closed] . [关闭]

converting NTP timestamps to Unix timestamps (struct timeval) involves two problems to be solved.将 NTP 时间戳转换为 Unix 时间戳(struct timeval)涉及两个需要解决的问题。

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. Unix 使用位于 1/1/1970-00:00h (UTC) 的纪元,NTP 使用 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 年的偏移量(两个日期之间有 17 个闰年,因此偏移量为

(70*365 + 17)*86400 = 2208988800

to be substracted from NTP time to get Unix struct timeval .从 NTP 时间中减去以获得 Unix struct timeval

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.第二个是struct timeval使用1/1000000 sec作为亚秒小数单位,NTP 使用1/2^32 sec作为小数时间单位。 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 .要将 NTP 转换为struct timeval可以将小数部分除以2^32 (这很容易,这是一个右移),然后乘以1000000 To cope with this, we have to use 64 bit arithmetic, as numbers range between 0 and 2^32 so, the best is:为了解决这个问题,我们必须使用 64 位算法,因为数字范围在02^32之间,所以最好的是:

  • 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.要将 NTP 转换为struct timeval请将小数部分字段(NTP 时间戳的右侧 32 位)复制到uint64_t变量并将其乘以1000000 ,然后将其右移 32 位位置以获得正确的值。 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.您必须考虑到 NTP 时间戳是按网络字节顺序排列的,因此也许您必须进行一些调整才能对数字进行操作。

  • 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)要从struct timeval转换,请将 unix 时间的tv_usec字段复制到uint64_t并将其左移 32 位位置,然后将其除以1000000并转换为网络字节顺序(最重要的字节在前)

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

The accepted answer is quite old and no longer compiles on GCC 9.3.0 but it has inspired me to write a C++ equivalent, which makes the conversions quite simple thanks to the chrono library:接受的答案已经很旧了,不再在 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM