Базовый клиент NTP в Windows в Visual C++

Добрый день,

Я пытаюсь реализовать очень простой клиент NTP, чтобы я мог запрашивать удаленный сервер NTP (то есть: pool.ntp.org) для интернет-времени, чтобы автоматически устанавливать время на моей доске разработки Windows при каждой загрузке.

Я рефакторинг кода здесь от другого пользователя:

Получить время/дату с сервера с помощью sntp(windows C++)

Но программа зависает на операции recv(). Разрешение DNS и операции send() выполняются без проблем.

Кто-нибудь знает, где я могу найти простой пример клиента NTP в Windows (GPL предпочтительнее, но на данный момент подойдет что угодно), или они могут прокомментировать, почему следующий блок кода будет зависать от примера (я не должен говорить " зависание", он просто никогда не получает ответа).

NTP_CLIENT.CPP

/******************************************************************************
 * Project Headers
 *****************************************************************************/
#include "stdafx.h"
#include "ntp_client.h"

/******************************************************************************
 * System Headers
 *****************************************************************************/
#include <winsock2.h>
#include <winsock.h>
#include <ws2tcpip.h>


/******************************************************************************
 * Preprocessor Directives and Macros
 *****************************************************************************/


/******************************************************************************
 * Class Member Function Definitions
 *****************************************************************************/
void Timestamp::ReverseEndian(void) {
    ReverseEndianInt(seconds);
    ReverseEndianInt(fraction);
}

time_t Timestamp::to_time_t(void) {
    return (seconds - ((70 * 365 + 17) * 86400))&0x7fffffff;
}

void NTPMessage::ReverseEndian(void) {
    ref.ReverseEndian();
    orig.ReverseEndian();
    rx.ReverseEndian();
    tx.ReverseEndian();
}

int NTPMessage::recv(int sock) {
    int ret = ::recv(sock, (char*)this, sizeof(*this), 0);
    ReverseEndian();
    return ret;
}

int NTPMessage::sendto(int sock, struct sockaddr_in* srv_addr) {
    ReverseEndian();
    int ret = ::sendto(sock, (const char*)this, sizeof(*this), 0, (sockaddr*)srv_addr, sizeof(*srv_addr));
    ReverseEndian();
    return ret;
}

void NTPMessage::clear() {
    memset(this, 0, sizeof(*this));
}

NTP_CLIENT.H

#ifndef __NTP_CLIENT_H__
#define __NTP_CLIENT_H__

#include <ctime>

#define ReverseEndianInt(x) ((x) = \
    ((x)&0xff000000) >> 24 |       \
    ((x)&0x00ff0000) >> 8  |       \
    ((x)&0x0000ff00) << 8  |       \
    ((x)&0x000000ff) << 24)

/**
 * NTP Fixed-Point Timestamp Format.
 * From [RFC 5905](http://tools.ietf.org/html/rfc5905).
 */
struct Timestamp {
    unsigned int seconds;   /**< Seconds since Jan 1, 1900. */
    unsigned int fraction;  /**< Fractional part of seconds. Integer number of 2^-32 seconds. */

    /**
     * Reverses the Endianness of the timestamp.
     * Network byte order is big endian, so it needs to be switched before
     * sending or reading.
     */
    void ReverseEndian(void);

    /**
     * Convert to time_t.
     * Returns the integer part of the timestamp in unix time_t format,
     * which is seconds since Jan 1, 1970.
     */
    time_t to_time_t(void);
};

/**
 * A Network Time Protocol Message.
 * From [RFC 5905](http://tools.ietf.org/html/rfc5905).
 */
struct NTPMessage {
    unsigned int mode :3;           /**< Mode of the message sender. 3 = Client, 4 = Server */
    unsigned int version :2;        /**< Protocol version. Should be set to 3. */
    unsigned int leap :2;           /**< Leap seconds warning. See the [RFC](http://tools.ietf.org/html/rfc5905#section-7.3) */
    unsigned char stratum;          /**< Servers between client and physical timekeeper. 1 = Server is Connected to Physical Source. 0 = Unknown. */
    unsigned char poll;             /**< Max Poll Rate. In log2 seconds. */
    unsigned char precision;        /**< Precision of the clock. In log2 seconds. */
    unsigned int sync_distance;     /**< Round-trip to reference clock. NTP Short Format. */
    unsigned int drift_rate;        /**< Dispersion to reference clock. NTP Short Format. */
    unsigned char ref_clock_id[4];  /**< Reference ID. For Stratum 1 devices, a 4-byte string. For other devices, 4-byte IP address. */
    Timestamp ref;                  /**< Reference Timestamp. The time when the system clock was last updated. */
    Timestamp orig;                 /**< Origin Timestamp. Send time of the request. Copied from the request. */
    Timestamp rx;                   /**< Recieve Timestamp. Reciept time of the request. */
    Timestamp tx;                   /**< Transmit Timestamp. Send time of the response. If only a single time is needed, use this one. */


    /**
     * Reverses the Endianness of all the timestamps.
     * Network byte order is big endian, so they need to be switched before
     * sending and after reading.
     *
     * Maintaining them in little endian makes them easier to work with
     * locally, though.
     */
    void ReverseEndian(void);

    /**
     * Recieve an NTPMessage.
     * Overwrites this object with values from the received packet.
     */
    int recv(int sock);

    /**
     * Send an NTPMessage.
     */
    int sendto(int sock, struct sockaddr_in* srv_addr);

    /**
     * Zero all the values.
     */
    void clear();
};

#endif  /* __NTP_CLIENT_H__ */

Брандмауэр и антивирусное программное обеспечение были отключены на тестируемом устройстве.

Спасибо.

ИЗМЕНИТЬ


Как и просили, это тело main:

WSADATA wsaData;
DWORD ret = WSAStartup(MAKEWORD(2,0), &wsaData);

char *host = "pool.ntp.org"; /* Don't distribute stuff pointing here, it's not polite. */
//char *host = "time.nist.gov"; /* This one's probably ok, but can get grumpy about request rates during debugging. */

NTPMessage msg;
/* Important, if you don't set the version/mode, the server will ignore you. */
msg.clear();
msg.version = 3;
msg.mode = 3 /* client */;

NTPMessage response;
response.clear();

int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in srv_addr;
memset(&srv_addr, 0, sizeof(srv_addr));
dns_lookup(host, &srv_addr); /* Helper function defined below. */

msg.sendto(sock, &srv_addr);
response.recv(sock);

time_t t = response.tx.to_time_t();
char *s = ctime(&t);
printf("The time is %s.", s);

WSACleanup();

Ссылки

  1. Получить время/дату с сервера с помощью sntp(windows C++)

person Cloud    schedule 27.12.2013    source источник
comment
Это не настоящий ответ, но вы знаете, что он встроен в Windows, верно? Панель управления -> Дата и время -> Интернет-время.   -  person TypeIA    schedule 27.12.2013
comment
Да, я знаю об этом. Я создаю простое приложение для запуска, которое выполняет множество проверок работоспособности и поддерживает множество параметров командной строки в зависимости от платы, на которой оно развернуто. Системные часы сбрасываются при каждом завершении работы и должны автоматически синхронизироваться с определенным NTP-сервером при каждой загрузке.   -  person Cloud    schedule 28.12.2013
comment
Пробовали ли вы использовать такую ​​программу, как Wireshark, для изучения/проверки правильности пакетов, сгенерированных вашей программой, или для проверки того, успешно ли программа отправляет эти пакеты с самого начала?   -  person JAB    schedule 28.12.2013
comment
@JAB Да. Исходящие пакеты кажутся правильными AFAIK, но я, похоже, не получаю никакого ответа. Я бы предположил, что мои пакеты либо искажены, либо есть что-то, что я полностью упустил.   -  person Cloud    schedule 28.12.2013
comment
Как собирается сообщение? Чтобы получить ответ, вам нужно отправить соответствующую информацию. Пожалуйста. предоставьте код, который устанавливает структуру msg.   -  person Arno    schedule 28.12.2013
comment
Я сделаю это сейчас. Спасибо.   -  person Cloud    schedule 29.12.2013
comment
Я бы рекомендовал заполнить поля version и mode, чтобы сервер знал, чего вы хотите. Как показал Роджер: первый байт 48-байтовой структуры сообщения содержит LI, VN, and Mode. Клиент не обязан предоставлять LI, но может пожелать предоставить VN и Mode. Вы хотите быть в режиме клиента (3) и, возможно, в режиме 3 или 4. Это ассемблируется в 0x1B или 0x27 соответственно. И еще: вам необходимо отправить эту информацию, прежде чем вы сможете что-либо прочитать.   -  person Arno    schedule 29.12.2013
comment
Просто из любопытства: каков размер структуры NTPMessage?   -  person Arno    schedule 29.12.2013


Ответы (1)


На рисунке 8 RFC 5905 (необходимо прокрутить вниз до страницы 18):

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Одна вещь, которую я вижу на рисунке 8, это то, что режим, версия и скачок должны составлять первый байт.

Измените режим, версию и типы перехода с unsigned int на unsigned char, чтобы ваши побитовые переменные занимали только 1 байт вместо 4. Это сделает размер вашего NTPMessage ожидаемым 48 байт.

как:

struct NTPMessage {
    unsigned char mode :3;
    unsigned char version :2;
    unsigned char leap :2;
    ...
person Roger Stewart    schedule 28.12.2013
comment
Согласно C99 6.7.2.1/9: Битовое поле интерпретируется как целочисленный тип со знаком или без знака, состоящий из указанного количества битов. Если значение 0 или 1 сохранено в битовом поле ненулевой ширины типа _Bool, значение битового поля должно сравниться с сохраненным значением. Код может быть определен реализацией и не переносим. - person Arno; 29.12.2013