Skip to content

Commit 939fba5

Browse files
committed
large upgrade
1 parent b83882c commit 939fba5

36 files changed

+3634
-673
lines changed

README.md

+266-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,266 @@
1-
# NTP Client (Windows Dll/Lib + console App)
2-
TBA
3-
![Console](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_console.png)
4-
TBA
1+
# NTP Client (Windows DLL/LIB + Console App + CVI GUI App)
2+
3+
1. [Time Synchronization via NTP](#1-Time-Synchronization-via-NTP)
4+
2. [NTP Client Library (C++ DLL)](#2-NTP-Client---Library-C++-DLL)
5+
2.1 [Description](#Description)
6+
2.2 [C++ interface](#C++-interface)
7+
2.3 [C interface](#C-interface)
8+
2.4 [Example of Use](#Example-of-Use)
9+
3. [NTP client - Graphical Interface (CVI)](#3-NTP-client---Graphical-Interface-CVI)
10+
11+
## 1. Time Synchronization via NTP
12+
13+
NTP has been used to synchronize time in variable response networks since
14+
1985 and that makes it one of the oldest Internet protocols. Uses UDP
15+
OSI layer 4 protocol and port 123. By default, it achieves an accuracy of 10 ms to 200
16+
µs, depending on the quality of the connection.
17+
18+
NTP uses a hierarchical system called "*stratum*". Server of type *stratum* 0
19+
obtains the most accurate time, for example, from a cesium clock, but is not intended for
20+
time distribution to the network. This is done by the server of type *stratum* 1, which it receives
21+
time from *loss* 0. Then there are servers *stratum* 2 to 15, which always
22+
they get the time from the parent server and their number basically shows
23+
distance from the reference clock.
24+
25+
The NTP algorithm begins by sending a defined packet (RFC 5905), respectively
26+
datagram, from client to server. The most important information transmitted by this packet
27+
are client mode (NTPv4), *stratum* local clock, accuracy of local clock,
28+
and especially the time **T1**, which indicates the time of the local clock at the time the packet leaves to
29+
networks. After the NTP server receives the packet, the server writes the time **T2** to it, which
30+
indicates the current time on the server clock and just before sending the time **T3**, which
31+
indicates the time the packet leaves back to the network. After receiving the packet by the client, it is finally
32+
writes the last time **T4**, which indicates the arrival back to the client. if they are
33+
these times are measured accurately, it is enough to calculate the two resulting ones thanks to the formulas below
34+
values. **Offset**, which symbolizes the shift of the client clock from the clock on the server and
35+
**Delay**, which represents the delay of the packet passing through the network, which can be due
36+
switches and network technologies are highly variable. The sum of these values then
37+
represents the final shift of the local clock, which should ideally be
38+
equal to zero.
39+
40+
$$
41+
Offset \ = \ \ frac {\ left (T2 \ - \ T1 \ right) \ + \ \ left (T3 \ - \ T4 \ right)} {2}
42+
$$
43+
44+
$$
45+
Delay \ = \ \ left (T4 \ - \ T1 \ right) \ + \ \ left (T3 \ - \ T2 \ right)
46+
$$
47+
48+
$$
49+
Delta \ = \ Offset \ + \ Delay \
50+
$$
51+
52+
## 2. NTP Client - Library (C++ DLL)
53+
54+
### 2.1 Description
55+
56+
I developed a simple and single-purpose library in C ++ in the environment
57+
Microsoft Visual Studio 2019. I only relied on the official RFC specification
58+
5905. The library is currently designed for Windows NT because it uses Win32
59+
API for reading and writing system time and *Winsock* for UDP communication. However
60+
in the future it is not a problem to extend it with directives \ #ifdef eg with POSIX
61+
*sockets*.
62+
63+
Because the library contains only one **Client** class, the class diagram is
64+
unnecessary.
65+
66+
´´´cpp
67+
class Client: public IClient
68+
´´´
69+
70+
The library has only two public methods, **query** and
71+
**query_and_sync**.
72+
73+
´´´cpp
74+
virtual Status query (const char* hostname, ResultEx** result_out);
75+
virtual Status query_and_sync (const char* hostname, ResultEx** result_out);
76+
´´´
77+
78+
Query is the core of the whole library. At the beginning of this method, a UDP is created first
79+
packet, it is filled with the current values ??I mentioned in the first chapter and
80+
sends it to the NTP server. Upon arrival, he completes the time T4 and performs the calculation, according to
81+
formulas from the first chapter. Times are represented by the time_point class from the library
82+
std :: chrono with resolution to nanoseconds (time **t1**) or class
83+
high_resolution_clock (time **t1_b**).
84+
85+
´´´cpp
86+
typedef std::chrono::time_point<std::chrono::system_clock,
87+
std::chrono::nanoseconds> time_point_t;
88+
time_point_t t1 = std::chrono::time_point_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now());
89+
auto t1_b = std::chrono::high_resolution_clock::now();
90+
´´´
91+
92+
This combination is because the times in the first formula (offset) must be
93+
absolute. These are the times **T2** and **T3** that came from the server. therefore not
94+
use high_resolution_clock, in the second formula (delay) it is then possible to read **T1**
95+
from **T4** use relative times, which can be obtained using high_resolution_clock.
96+
The following formulas show the calculation using this approach, all units
97+
variables are nanoseconds.
98+
99+
´´´cpp
100+
offset = [(T2 - T1) + (T3 - T4)] / 2
101+
delay = (**T4b** - **T1b**) - (T3 - T2)
102+
´´´
103+
104+
By summing the values *offset* and *delay* we get *delta*, ie the value by which
105+
adjust the local system clock. However, this only applies if the latter is used
106+
public methods **query_and_sync**, the first mentioned will only communicate with
107+
server and calculation.
108+
109+
Resulting calculated and obtained values, including *jitter* (stability indicators
110+
network connection) is returned to the user either in the Result structure that is used for
111+
classic C interface, or in the class ResultEx, which, unlike the first contains
112+
time represented by class time_point_t, as opposed to time represented by classical
113+
TimePt structure with *integers*.
114+
115+
´´´cpp
116+
struct Result
117+
{
118+
struct TimePt time;
119+
struct Metrics mtr;
120+
};
121+
´´´
122+
´´´cpp
123+
class ResultEx
124+
{
125+
public:
126+
time_point_t time;
127+
Metrics mtr;
128+
};
129+
´´´
130+
131+
This achieves the compatibility between C and C ++, which is required for a dynamic library.
132+
If the user uses the library directly from C ++, it is more convenient to work with time
133+
represented by the time_point_t class, otherwise there is no choice but to use it
134+
structure.
135+
136+
´´´cpp
137+
struct TimePt
138+
{
139+
int tm_nsec;
140+
int tm_usec;
141+
int tm_msec;
142+
int tm_sec;
143+
int tm_min;
144+
int tm_hour
145+
int tm_mday;
146+
int tm_mon;
147+
int tm_year;
148+
};
149+
´´´
150+
´´´cpp
151+
struct Metrics
152+
{
153+
double delay_ns;
154+
double offset_ns;
155+
double jitter_ns;
156+
double delta_ns;
157+
};
158+
´´´
159+
160+
Error states are returned as *enumerator* Status, where 0 means success
161+
(similar to POSIX) and anything else is a bug.
162+
163+
´´´cpp
164+
enum Status : int16_t
165+
{
166+
OK = 0,
167+
UNKNOWN_ERR = 1,
168+
INIT_WINSOCK_ERR = 2,
169+
CREATE_SOCKET_ERR = 3,
170+
SEND_MSG_ERR = 4,
171+
RECEIVE_MSG_ERR = 5,
172+
RECEIVE_MSG_TIMEOUT = 6,
173+
SET_WIN_TIME_ERR = 7,
174+
ADMIN_RIGHTS_NEEDED = 8
175+
};
176+
´´´
177+
178+
In addition, the library contains several static stateless methods to facilitate the work
179+
programmer, used primarily to format results and convert types.
180+
181+
### 2.2 C++ interface
182+
183+
The standard library interface for use with object-oriented languages is in
184+
form *interface*, which exposes the two main public methods described above
185+
**query** a **query_and_sync**. The interface is only a macro for the struct type,
186+
of course you could use a proprietary MS **__interface**, but most of the time it gets better
187+
stick to proven and compatible things.
188+
189+
´´´cpp
190+
Interface IClient
191+
{
192+
virtual Status query(const char* hostname, ResultEx** result_out) = 0;
193+
virtual Status query_and_sync(const char* hostname, ResultEx**result_out) = 0;
194+
virtual ~IClient() {};
195+
};
196+
´´´
197+
198+
### 2.3 C interface
199+
200+
The interface usable for DLL calls must be compatible with classic ANSI C,
201+
instead of classes, it is necessary to use the classic C OOP style, namely functions, structures and
202+
*opaque pointers*. These functions must then be exported using the EXPORT macro,
203+
which is a macro for **__declspec (dllexport)**. It is also necessary to set adequate
204+
calling convention, in our case it is **__cdecl**, where the one calling as well
205+
cleans the tray.
206+
207+
The **Client__create** function creates a library instance, which is represented
208+
pointer, or macro, HNTP, which in the context of Windows is called
209+
*handle*.
210+
211+
´´´cpp
212+
typedef void* HNTP;
213+
´´´
214+
215+
Other functions, such as **Client__query** or **Client__query_and_sync**
216+
they take this indicator as the first argument. The rest is very similar to C++
217+
interface, however one difference it has. Instead of delete, it must be called at the end
218+
**Client__free_result** and **Client__close**.
219+
220+
´´´cpp
221+
extern "C"
222+
{
223+
/* object lifecycle */
224+
EXPORT HNTP __cdecl Client__create(void);
225+
EXPORT void __cdecl Client__close(HNTP self);
226+
227+
/* main NTP server query functions */
228+
EXPORT enum Status __cdecl Client__query(HNTP self, const char* hostname, struct Result** result_out);
229+
EXPORT enum Status __cdecl Client__query_and_sync(HNTP self, const char* hostname, struct Result** result_out);
230+
231+
/* helper functions */
232+
EXPORT void __cdecl Client__format_info_str(struct Result* result, char* str_out);
233+
EXPORT void __cdecl Client__get_status_str(enum Status status, char* str_out);
234+
EXPORT void __cdecl Client__free_result(struct Result* result);
235+
}
236+
´´´
237+
238+
### 2.4 Example of Use
239+
240+
You must have *runtime* **vc_redist** (2015-19) installed to run. Code
241+
it is at least partially annotated and perhaps even clear. I tried to make it
242+
use trivial. A client instance is created, the *query* function is called, and it terminates
243+
the client. This can be done in an infinite loop with a defined interval,
244+
to ensure constant time synchronization. The following lines are excluded
245+
from a console application that serves as an example of use.
246+
247+
´´´cpp
248+
enum Status s;
249+
struct Result* result = nullptr;
250+
HNTP client = Client__create()
251+
s = Client__query_and_sync(client, "195.113.144.201", &result);
252+
Client__free_result(result);
253+
Client__close(client);
254+
´´´
255+
256+
![Console](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_console2.png)
257+
258+
## 3. NTP client - Graphical Interface (CVI)
259+
260+
I used the dynamic library in the LabWindows / CVI environment to create
261+
graphical interface of the NTP client, which is periodically called from its own thread. On
262+
graph we then see the green delta value (the current difference of the local clock from
263+
server), its diameter in yellow and *jitter* in network communication in red. To run
264+
**CVI Runtime 2019** is required.
265+
266+
![CVI GUI](https://raw.githubusercontent.com/parezj/NTP-Client/master/img/ntp_client_gui.png)

build/ntp_client.dll

18.5 KB
Binary file not shown.

build/ntp_client_console.exe

19.5 KB
Binary file not shown.

build/ntp_client_gui.exe

640 KB
Binary file not shown.

doc/VIN_NTP_klient.pdf

1.28 MB
Binary file not shown.

img/ntp_client_console.png

87.1 KB
Loading

img/ntp_client_console2.png

61.9 KB
Loading

img/ntp_client_gui.png

71.7 KB
Loading

lib/vcredist.png

10.8 KB
Loading

lib/vcredist_x64.exe

-14.3 MB
Binary file not shown.

lib/vcredist_x86.exe

-13.7 MB
Binary file not shown.

src/ctu_vin_ntp_client.sln

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ntp_client_lib", "ntp_clien
99
EndProject
1010
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ntp_client_shared", "ntp_client_shared\ntp_client_shared.vcxitems", "{CE1844D1-F6F4-40D9-85B6-530A3857450C}"
1111
EndProject
12-
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ntp_client_test", "ntp_client_test\ntp_client_test.vcxproj", "{F8635A97-994B-4240-8495-DFA5A15DF321}"
12+
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ntp_client_test", "ntp_client_console\ntp_client_console.vcxproj", "{F8635A97-994B-4240-8495-DFA5A15DF321}"
1313
EndProject
1414
Global
1515
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**************************************
2+
* Author: Jakub Parez
3+
* File: ntp_client_console.cpp
4+
* Project: NTP client
5+
* Company: CTU/VIN
6+
* Date: 2020/4/29 11:25
7+
* License: MIT
8+
***************************************/
9+
10+
#include <iostream>
11+
#include "ntp_client.h"
12+
13+
#pragma warning(disable:4996) // disable CRT secure warnings
14+
15+
#define IP "195.113.144.201" // NTP server ip <tik.cesnet.cz>
16+
#define INTERVAL 1000 // interval of NTP server query
17+
18+
using namespace CTU::VIN::NTP_client;
19+
20+
BOOL WINAPI ConsoleHandler(DWORD dwCtrlType); // console ctrl handler prototype
21+
22+
HNTP client; // NTP client object opaque pointer
23+
24+
int main(int argc, char** argv) // C style example
25+
{
26+
enum Status s; // status enum as a result from query
27+
struct Result* result = nullptr; // result struct with all data
28+
char status_str[100]; // status string from enum container
29+
char result_str[400]; // result print info char container
30+
char ip[20]; // IP char container
31+
32+
if (argc > 1) // if there exists argument,
33+
strncpy(ip, argv[1], 20); // copy it into IP array
34+
else
35+
strcpy(ip, IP); // otherwise use predefined IP value
36+
37+
SetConsoleCtrlHandler(ConsoleHandler, true); // assign console window onclose event
38+
client = Client__create(); // finally create NTP client
39+
40+
while (true)
41+
{
42+
try
43+
{
44+
s = Client__query_and_sync(client, ip, &result); // main NTP server query
45+
46+
Client__get_status_str(s, status_str); // convert status enum to str
47+
printf("\nStatus: %s\n", status_str); // print status string
48+
49+
if (s == Status::OK) { // if status ok, print info
50+
Client__format_info_str(result, result_str); // format info into string
51+
Client__free_result(result); // delete dynamic object
52+
printf("%s", result_str); // print all important info
53+
}
54+
else { // if status not ok, quit
55+
Client__free_result(result); // delete dynamic object
56+
break;
57+
}
58+
Sleep(INTERVAL); // sleep for specified time
59+
}
60+
catch (const std::exception& exc) // catch any exception
61+
{
62+
printf("%s", exc.what()); // print exception details
63+
break; // quit
64+
}
65+
}
66+
getchar(); // finally wait for user input
67+
}
68+
69+
BOOL WINAPI ConsoleHandler(DWORD dwCtrlType) // console ctrl event handler
70+
{
71+
if (CTRL_CLOSE_EVENT == dwCtrlType) // close window event fired
72+
{
73+
if (client != NULL) // if client exists,
74+
{
75+
Client__close(client); // close / clean-up
76+
}
77+
}
78+
return TRUE;
79+
}

0 commit comments

Comments
 (0)