|
1 |
| -# NTP Client (Windows Dll/Lib + console App) |
2 |
| -TBA |
3 |
| - |
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 | + |
| 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 | + |
0 commit comments