无限循环中的NTP客户端(不工作)

时间:2017-12-05 10:18:45

标签: c sockets time server ntp

我根据给定here的参考设计了一个客户端。

我修改了代码(见下文)以满足这些要求

  1. 以毫秒为单位打印时间戳

  2. 在无限循环内获取NTP时间戳,RTT等。

  3. 定义一个周期时间(每x秒获得一次)

  4. #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <time.h>
    
    #define NTP_TIMESTAMP_DELTA 2208988800ull
    #define ENDIAN_SWAP32(data)                                                    \
      ((data >> 24) |               /* right shift 3 bytes */                      \
       ((data & 0x00ff0000) >> 8) | /* right shift 1 byte */                       \
       ((data & 0x0000ff00) << 8) | /* left shift 1 byte */                        \
       ((data & 0x000000ff) << 24)) /* left shift 3 bytes */
    
    void error(char *msg) {
      perror(msg); // Print the error message to stderr.
      exit(0);     // Quit the process.
    }
    
    int main(int argc, char *argv[]) {
      int sockfd, n;    // Socket file descriptor and the n return result from
                        // writing/reading from the socket.
      int portno = 123; // NTP UDP port number
      int i, z = 0;
      struct timeval tv1, tv2;
    
      typedef struct {
        unsigned li : 2; // Only two bits. Leap indicator.
        unsigned vn : 3; // Only three bits. Version number of the protocol.
        unsigned
            mode : 3; // Only three bits. Mode. Client will pick mode 3 for client.
    
        uint8_t stratum; // Eight bits. Stratum level of the local clock.
        uint8_t poll; // Eight bits. Maximum interval between successive messages.
        uint8_t precision; // Eight bits. Precision of the local clock.
    
        uint32_t rootDelay; // 32 bits. Total round trip delay time.
        uint32_t
            rootDispersion; // 32 bits. Max error aloud from primary clock source.
        uint32_t refId;     // 32 bits. Reference clock identifier.
    
        uint32_t refTm_s; // 32 bits. Reference time-stamp seconds.
        uint32_t refTm_f; // 32 bits. Reference time-stamp fraction of a second.
    
        uint32_t origTm_s; // 32 bits. Originate time-stamp seconds.
        uint32_t origTm_f; // 32 bits. Originate time-stamp fraction of a second.
    
        uint32_t rxTm_s; // 32 bits. Received time-stamp seconds.
        uint32_t rxTm_f; // 32 bits. Received time-stamp fraction of a second.
    
        uint32_t txTm_s; // 32 bits and the most important field the client cares
                         // about. Transmit time-stamp seconds.
        uint32_t txTm_f; // 32 bits. Transmit time-stamp fraction of a second.
    
      } ntp_packet; // Total: 384 bits or 48 bytes.
    
      // Create and zero out the packet. All 48 bytes worth.
    
      ntp_packet packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
      memset(&packet, 0, sizeof(ntp_packet));
      *((char *)&packet + 0) =
          0x1b; // Represents 27 in base 10 or 00011011 in base 2.
    
      uint8_t *ptr = (uint8_t *)(&packet); /* to read raw bytes */
      struct sockaddr_in serv_addr;        // Server address data structure.
      struct hostent *server;              // Server data structure.
    
      sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // Create a UDP socket.
    
      if (sockfd < 0)
        error("ERROR opening socket");
    
      server = gethostbyname(argv[1]); // Convert URL to IP.
    
      if (server == NULL)
        error("ERROR, no such host");
      // Zero out the server address structure.
      bzero((char *)&serv_addr, sizeof(serv_addr));
      serv_addr.sin_family = AF_INET;
      // Copy the server's IP address to the server address structure.
      bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr,
            server->h_length);
      // Convert the port number integer to network big-endian style and save it to
      // the server address structure.
      serv_addr.sin_port = htons(portno);
      // Call up the server using its IP address and port number.
      if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");
      printf("\nNTP client started \n\n");
    
      while (1) {
    
        z = z + 1;
        ntp_packet packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        memset(&packet, 0, sizeof(ntp_packet));
        *((char *)&packet + 0) =
            0x1b; // Represents 27 in base 10 or 00011011 in base 2.
        uint8_t *ptr = (uint8_t *)(&packet); /* to read raw bytes */
        gettimeofday(&tv1, NULL);
        unsigned long long millisecondsSinceEpochStart =
            (unsigned long long)(tv1.tv_sec) * 1000 +
            (unsigned long long)(tv1.tv_usec) / 1000;
        n = write(sockfd, (char *)&packet, sizeof(ntp_packet));
    
        if (n < 0)
          error("ERROR writing to socket");
    
        n = read(sockfd, (char *)&packet, sizeof(ntp_packet));
        if (n < 0)
          error("ERROR reading from socket");
        gettimeofday(&tv2, NULL);
        unsigned long long millisecondsSinceEpochEnd =
            (unsigned long long)(tv2.tv_sec) * 1000 +
            (unsigned long long)(tv2.tv_usec) / 1000;
    
        packet.precision = ntohl(packet.precision); // Precision
    
        packet.rootDelay = ntohl(packet.rootDelay);
    
        packet.rxTm_s = ntohl(packet.rxTm_s); // Time-stamp seconds.
        packet.rxTm_f = ntohl(packet.rxTm_f); // Time-stamp fraction of a second.
    
        packet.txTm_s = ntohl(packet.txTm_s); // Time-stamp seconds.
        packet.txTm_f = ntohl(packet.txTm_f); // Time-stamp fraction of a second.
    
        // packet.rootDelay = ntohl(packet.rootDelay); // RTT
        // time_t rootDelay = ( time_t ) ( packet.rootDelay - NTP_TIMESTAMP_DELTA );
    
        printf("\n................................................................."
               "...\n");
        printf("\nIteration Number : %d\n", z);
        printf("..................................................................."
               "..\n");
        printf("NTP Telegram Contains\n");
        for (i = 0; i < sizeof(ntp_packet); i++) {
          if (i != 0 && i % 8 == 0)
            printf("\n");
          printf("0x%2x ", ptr[i]);
        }
        printf("\n\n\n");
    
        printf("Client Send Timestamp T1(ms): %llu\n", millisecondsSinceEpochStart);
    
        printf("server Recieve Timestamp T2: %llu.", packet.rxTm_s);
        printf("%llu\n", packet.rxTm_f);
    
        printf("server Transmit Timestamp T3: %llu.", packet.txTm_s);
        printf("%llu\n", packet.txTm_f);
    
        printf("Client Recieve Timestamp T4(ms): %llu\n",
               millisecondsSinceEpochEnd);
    
        printf("RTT (calculated by NTP): %llu\n", packet.rootDelay);
    
        printf("Precision (calculated by NTP): %llu\n", packet.precision);
    
        sleep(atoi(argv[2]));
      }
      return 0;
      close(sockfd);
    }
    

    Makefile:

    .PHONY: all clean tags
    
    CFLAGS := -Wall -O2
    
    all: client 
    
    client: client.c
        $(CC) $(CFLAGS) $^ -o $@
        strip -s $@
    
    tags:
        ctags -R .
    
    clean:
        -rm *.o client 
    

    使用

    制作并运行代码
    ./client 0.pool.ntp.org 1
    

    我面临的问题,需要您的建议可能的代码更改和改进

    1. 代码无限制地工作,它在两者之间停止,并且在一些迭代之后不会打印任何输出(没有显示错误消息)

    2. 虽然64

    3. ,但时间戳的大小不同
    4. RTT和精确打印相同的值,并且在每次迭代中都没有更新(有时两个都显示0

    5. 如何从T2packet.rxTm_s构建packet.rxTm_f作为64位的一个时间戳。

2 个答案:

答案 0 :(得分:1)

当我用一些警告标志编译你的代码时,我收到了一些警告:

  

25警告已生成。

有些显然没用,但有些确实很可怕:

warning: implicit conversion changes signedness: 'int' to 'size_t' (aka 'unsigned long') [-Wsign-conversion]
        server->h_length);
        ~~~~~~~~^~~~~~~~
warning: declaration shadows a local variable [-Wshadow]
    ntp_packet packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
               ^
note: previous declaration is here
  ntp_packet packet = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
             ^
warning: declaration shadows a local variable [-Wshadow]
    uint8_t *ptr = (uint8_t *)(&packet); /* to read raw bytes */
             ^
note: previous declaration is here
  uint8_t *ptr = (uint8_t *)(&packet); /* to read raw bytes */
           ^
warning: implicit declaration of function 'gettimeofday' is invalid in C99 [-Wimplicit-function-declaration]
    gettimeofday(&tv1, NULL);
    ^
warning: implicit declaration of function 'write' is invalid in C99 [-Wimplicit-function-declaration]
    n = write(sockfd, (char *)&packet, sizeof(ntp_packet));
        ^
warning: implicit declaration of function 'read' is invalid in C99 [-Wimplicit-function-declaration]
    n = read(sockfd, (char *)&packet, sizeof(ntp_packet));
        ^
warning: implicit conversion loses integer precision: 'unsigned int' to 'uint8_t' (aka 'unsigned char') [-Wconversion]
    packet.precision = ntohl(packet.precision); // Precision
                     ~ ^~~~~~~~~~~~~~~~~~~~~~~
warning: format specifies type 'unsigned long long' but the argument has type 'uint32_t' (aka 'unsigned int') [-Wformat]
    printf("server Recieve Timestamp T2: %llu.", packet.rxTm_s);
                                         ~~~~    ^~~~~~~~~~~~~
                                         %u
warning: format specifies type 'unsigned long long' but the argument has type 'uint32_t' (aka 'unsigned int') [-Wformat]
    printf("%llu\n", packet.rxTm_f);
            ~~~~     ^~~~~~~~~~~~~
            %u
warning: format specifies type 'unsigned long long' but the argument has type 'uint32_t' (aka 'unsigned int') [-Wformat]
    printf("server Transmit Timestamp T3: %llu.", packet.txTm_s);
                                          ~~~~    ^~~~~~~~~~~~~
                                          %u
warning: format specifies type 'unsigned long long' but the argument has type 'uint32_t' (aka 'unsigned int') [-Wformat]
    printf("%llu\n", packet.txTm_f);
            ~~~~     ^~~~~~~~~~~~~
            %u
warning: format specifies type 'unsigned long long' but the argument has type 'uint32_t' (aka 'unsigned int') [-Wformat]
    printf("RTT (calculated by NTP): %llu\n", packet.rootDelay);
                                     ~~~~     ^~~~~~~~~~~~~~~~
                                     %u
warning: format specifies type 'unsigned long long' but the argument has type 'uint8_t' (aka 'unsigned char') [-Wformat]
    printf("Precision (calculated by NTP): %llu\n", packet.precision);
                                           ~~~~     ^~~~~~~~~~~~~~~~
                                           %hhu
warning: implicit declaration of function 'sleep' is invalid in C99 [-Wimplicit-function-declaration]
    sleep(atoi(argv[2]));
    ^
warning: implicit declaration of function 'close' is invalid in C99 [-Wimplicit-function-declaration]
  close(sockfd);
  ^
warning: unused parameter 'argc' [-Wunused-parameter]
int main(int argc, char *argv[]) {
             ^
warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare]
    for (i = 0; i < sizeof(ntp_packet); i++) {
                ~ ^ ~~~~~~~~~~~~~~~~~~
warning: 'return' will never be executed [-Wunreachable-code-return]
  return 0;
         ^
warning: code will never be executed [-Wunreachable-code]
  close(sockfd);

我没有保留所有这些,但仍然 19 !首先,你应该修复所有这些,因为它们在我看来很重要,忽略那些可能会导致未定义的行为。清除所有这些警告后,您可以再次尝试调试代码。 (尝试使用clang -Weverything获得零警告;并非所有警告都有用,但在忽略它们之前尝试理解它们。)

接下来,我测试了你的代码,它似乎可以在我的机器上运行。但是,代码中存在许多潜在的未定义行为。位字段非常不方便,因为它们的布局是实现定义的。

尽管如此,我得到了这个输出:

....................................................................
Iteration Number : 42
.....................................................................
NTP Telegram Contains
0x1c 0x 2 0x 3 0x 0 0xd7 0x 7 0x 0 0x 0
0x 0 0x 0 0x 8 0xad 0xf7 0x5c 0x9c 0x6b
0xdd 0xd1 0xe8 0x64 0xc1 0x31 0xc9 0x4a
0x 0 0x 0 0x 0 0x 0 0x 0 0x 0 0x 0 0x 0
0xca 0xe8 0xd1 0xdd 0x66 0x71 0xa4 0x25
0xca 0xe8 0xd1 0xdd 0xb8 0xb0 0xa6 0x25

Client Send Timestamp T1(ms): 1512536048971
server Recieve Timestamp T2: 3721521354.631533926
server Transmit Timestamp T3: 3721521354.631681208
Client Recieve Timestamp T4(ms): 1512536048973
RTT (calculated by NTP): 2007
Precision (calculated by NTP): 0

// after some iteration

....................................................................
Iteration Number : 69
.....................................................................
NTP Telegram Contains
0x1c 0x 2 0x 3 0x 0 0xd7 0x 7 0x 0 0x 0
0x 0 0x 0 0x 8 0xc7 0xf7 0x5c 0x9c 0x6b
0xdd 0xd1 0xe8 0x64 0xc1 0x31 0xc9 0x4a
0x 0 0x 0 0x 0 0x 0 0x 0 0x 0 0x 0 0x 0
0xe5 0xe8 0xd1 0xdd 0xcf 0x58 0x20 0x37
0xe5 0xe8 0xd1 0xdd 0x86 0xaf 0x22 0x37

Client Send Timestamp T1(ms): 1512536076040
server Recieve Timestamp T2: 3721521381.924866767
server Transmit Timestamp T3: 3721521381.925020038
Client Recieve Timestamp T4(ms): 1512536076042
RTT (calculated by NTP): 2007
Precision (calculated by NTP): 0

这对我来说很好看。

所以回答你的问题:

  1. 代码没有在我的机器中停止并按预期工作。当然,您的代码停止的原因显而易见:您已调用write()read(),它们可能会阻塞,直到I / O完成。请记住,您正在使用UDP协议,某些数据包永远不会到达服务器,或者某些来自服务器的数据包永远不会到达您。如果您想对真实服务器进行编码,请使用select()poll()epoll()之类的内容。
  2. NTP的版本为4,您的代码要求30x1b; // Represents 27 in base 10 or 00011011 in base 2. =&gt; 3版本字段。顺便提一下,您创建一个位字段...使用它;不要使用魔法数字)。所以我想你最终会混合使用版本34;我不明白的是,NTP的文档声称该协议是向后兼容的。要回答这个问题,您应该阅读NTP协议的RFC V3(已废弃)。
  3. 我不知道它是什么以及为什么它没有改变。
  4. 我不知道。
  5. 所以,如果我没有给你 你问题的答案,你会问我为什么要回答你?这是因为你明显误解了批判性的事情。

    首先这个教程是********(Linus&#39;最喜欢的单词),你不能期望在最后RFC时用C语言中的30行代码解析协议NTP协议的行6163行!如果要实现自己的解析器,必须才能阅读此协议。

    其次,本教程的代码编写得很糟糕,我的&#34; wtf-o-matic&#34;通过线&#34;向我展示了非常高的&#34; wtf (比失败更糟糕)。为什么使用unsigned而不是uint8_t用于位字段(位字段仍然是80%实现定义)?为什么ntp_packet packet = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };而不是ntp_packet packet = {0};?为什么以及为什么以及为什么在......之后使用memset(&packet, 0, sizeof(ntp_packet));?什么是*((char *)&packet + 0) = 0x1b;,这是***** ******!好吧,还有更多话要说,但我建议您使用getaddrinfo()代替gethostbyname()htons()等所有混乱......

    作为结论:阅读我链接的RFC(NTP V4);并停止使用这个&#34;教程&#34;。如果你想实现你自己的NTP库(好消息即将到来),你可以使用RFC提供的漂亮的 skeleton ,我很高兴在RFC中找到一个代码示例,我做了一个gist of the example对你而言。好消息是RFC在文档行方面减少了一半来阅读!所以GL,HF在你的演讲中。

答案 1 :(得分:0)

我已阅读C代码并同意&#34;质量&#34;根据要做的事情,这段代码。

对于分析,我不同意这一点: - 我不认为问题是实时问题,可以通过在NTP协议上使用wireshark和过滤器轻松检查。尽管程序失败了,但wireshark仍然应该在以太网接口上捕获数据。 - 对我来说,问题似乎是由Kiss-o&-Dath-Packet引起的(参见RFC 5905的7.4节)。 - wirehark捕获将有助于了解真正发生的事情并进行更深入的调查。