Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_clock_sync.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <errno.h>
45#include <getopt.h>
46
47#include "nilorea/n_common.h"
48#include "nilorea/n_log.h"
49#include "nilorea/n_network.h"
51
52#define MODE_SERVER 0
53#define MODE_CLIENT 1
54
56#define MSG_SYNC "SYNC"
58#define MSG_RESP "RESP"
59
60static void usage(const char* prog) {
61 fprintf(stderr,
62 "Usage:\n"
63 " Server: %s -p PORT [-o OFFSET] [-V LOGLEVEL]\n"
64 " Client: %s -s ADDRESS -p PORT [-n COUNT] [-V LOGLEVEL]\n"
65 "\n"
66 "Options:\n"
67 " -p PORT port to use\n"
68 " -s ADDRESS server address (client mode)\n"
69 " -o OFFSET simulated clock offset in seconds (server only, default: 0.42)\n"
70 " -n COUNT number of sync rounds (client only, default: 20)\n"
71 " -V LEVEL log level: LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_ERR\n"
72 " -h show this help\n",
73 prog, prog);
74}
75
76static double get_time(void) {
77 struct timespec ts;
78 clock_gettime(CLOCK_MONOTONIC, &ts);
79 return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
80}
81
82static void run_server(char* port, double fake_offset, int nb_rounds) {
83 NETWORK* netw = NULL;
84
85 n_log(LOG_NOTICE, "Server: binding UDP on port %s (simulated offset: %.4f s)", port, fake_offset);
86
87 if (netw_bind_udp(&netw, NULL, port, NETWORK_IPALL) == FALSE) {
88 n_log(LOG_ERR, "Server: failed to bind UDP on port %s", port);
89 return;
90 }
91
92 n_log(LOG_NOTICE, "Server: listening for sync requests...");
93
94 for (int i = 0; i < nb_rounds; i++) {
95 char buf[256] = "";
96 struct sockaddr_storage client_addr;
97 socklen_t client_len = sizeof(client_addr);
98
99 ssize_t received = netw_udp_recvfrom(netw, buf, sizeof(buf) - 1,
100 (struct sockaddr*)&client_addr, &client_len);
101 if (received <= 0) {
102 n_log(LOG_ERR, "Server: recv error");
103 continue;
104 }
105 buf[received] = '\0';
106
107 /* parse: "SYNC client_send_time" */
108 double client_send_time = 0.0;
109 if (sscanf(buf, MSG_SYNC " %lf", &client_send_time) != 1) {
110 n_log(LOG_ERR, "Server: malformed request: %s", buf);
111 continue;
112 }
113
114 /* server time = local time + fake offset to simulate a different clock */
115 double server_time = get_time() + fake_offset;
116
117 /* respond: "RESP client_send_time server_time" */
118 char resp[256];
119 snprintf(resp, sizeof(resp), MSG_RESP " %.9f %.9f", client_send_time, server_time);
120
121 ssize_t sent = netw_udp_sendto(netw, resp, (uint32_t)strlen(resp),
122 (struct sockaddr*)&client_addr, client_len);
123 if (sent <= 0) {
124 n_log(LOG_ERR, "Server: send error");
125 } else {
126 n_log(LOG_DEBUG, "Server: responded to sync #%d (server_time=%.4f)", i + 1, server_time);
127 }
128 }
129
130 n_log(LOG_NOTICE, "Server: done after %d rounds", nb_rounds);
132}
133
134static void run_client(char* server, char* port, int nb_rounds) {
135 NETWORK* netw = NULL;
136
137 n_log(LOG_NOTICE, "Client: connecting to %s:%s for %d sync rounds", server, port, nb_rounds);
138
139 if (netw_connect_udp(&netw, server, port, NETWORK_IPALL) != TRUE) {
140 n_log(LOG_ERR, "Client: failed to connect UDP to %s:%s", server, port);
141 return;
142 }
143
145 if (!cs) {
146 n_log(LOG_ERR, "Client: failed to create clock sync estimator");
148 return;
149 }
150
151 n_log(LOG_NOTICE, "Client: starting clock synchronization...");
152 n_log(LOG_NOTICE, "");
153 n_log(LOG_NOTICE, " Round | RTT (ms) | Offset (ms) | Est.Offset (ms) | Est.RTT (ms)");
154 n_log(LOG_NOTICE, "-------+-----------+-------------+-----------------+-------------");
155
156 int rounds_done = 0;
157 while (rounds_done < nb_rounds) {
158 double now = get_time();
159
160 if (!n_clock_sync_should_send(cs, now)) {
161 u_sleep(50000); /* 50ms poll */
162 continue;
163 }
164
165 /* send sync request */
166 double send_time = get_time();
167 n_clock_sync_mark_sent(cs, send_time);
168
169 char msg[256];
170 snprintf(msg, sizeof(msg), MSG_SYNC " %.9f", send_time);
171 ssize_t sent = send_udp_data(netw, msg, (uint32_t)strlen(msg));
172 if (sent <= 0) {
173 n_log(LOG_ERR, "Client: send error");
174 break;
175 }
176
177 /* wait for response */
178 char buf[256] = "";
179 ssize_t received = recv_udp_data(netw, buf, sizeof(buf) - 1);
180 if (received <= 0) {
181 n_log(LOG_ERR, "Client: recv error or timeout");
182 rounds_done++;
183 continue;
184 }
185 buf[received] = '\0';
186
187 double recv_time = get_time();
188
189 /* parse: "RESP client_send_time server_time" */
190 double resp_client_time = 0.0, resp_server_time = 0.0;
191 if (sscanf(buf, MSG_RESP " %lf %lf", &resp_client_time, &resp_server_time) != 2) {
192 n_log(LOG_ERR, "Client: malformed response: %s", buf);
193 rounds_done++;
194 continue;
195 }
196
197 /* feed the sample into the estimator */
198 n_clock_sync_process_response(cs, resp_client_time, resp_server_time, recv_time);
199
200 rounds_done++;
201
202 double raw_rtt = (recv_time - resp_client_time) * 1000.0;
203 double raw_offset = (resp_server_time + (recv_time - resp_client_time) / 2.0 - recv_time) * 1000.0;
204
205 n_log(LOG_NOTICE, " %5d | %9.3f | %11.3f | %15.3f | %11.3f",
206 rounds_done,
207 raw_rtt,
208 raw_offset,
209 cs->estimated_offset * 1000.0,
210 cs->estimated_rtt * 1000.0);
211 }
212
213 n_log(LOG_NOTICE, "-------+-----------+-------------+-----------------+-------------");
214 n_log(LOG_NOTICE, "");
215 n_log(LOG_NOTICE, "Final estimated offset: %.4f ms (%.6f s)", cs->estimated_offset * 1000.0, cs->estimated_offset);
216 n_log(LOG_NOTICE, "Final estimated RTT: %.4f ms (%.6f s)", cs->estimated_rtt * 1000.0, cs->estimated_rtt);
217 n_log(LOG_NOTICE, "");
218
219 /* demonstrate n_clock_sync_server_time */
220 double local_now = get_time();
221 double est_server_now = n_clock_sync_server_time(cs, local_now);
222 n_log(LOG_NOTICE, "Local time: %.6f", local_now);
223 n_log(LOG_NOTICE, "Estimated server time: %.6f", est_server_now);
224 n_log(LOG_NOTICE, "Difference: %.4f ms", (est_server_now - local_now) * 1000.0);
225
228}
229
230int main(int argc, char* argv[]) {
231 int mode = -1;
232 char* server = NULL;
233 char* port = NULL;
234 int nb_rounds = 20;
235 double fake_offset = 0.42;
236 int log_level = LOG_NOTICE;
237 int getoptret = 0;
238
240
241 if (argc == 1) {
242 usage(argv[0]);
243 return 1;
244 }
245
246 while ((getoptret = getopt(argc, argv, "hs:p:n:o:V:")) != EOF) {
247 switch (getoptret) {
248 case 'h':
249 usage(argv[0]);
250 return 0;
251 case 's':
252 server = strdup(optarg);
254 break;
255 case 'p':
256 port = strdup(optarg);
257 break;
258 case 'n':
259 nb_rounds = atoi(optarg);
260 if (nb_rounds < 1) nb_rounds = 1;
261 break;
262 case 'o':
263 fake_offset = atof(optarg);
264 break;
265 case 'V':
266 if (!strncmp("LOG_DEBUG", optarg, 9)) {
268 } else if (!strncmp("LOG_INFO", optarg, 8)) {
270 } else if (!strncmp("LOG_NOTICE", optarg, 10)) {
272 } else if (!strncmp("LOG_ERR", optarg, 7)) {
274 } else {
275 fprintf(stderr, "Unknown log level: %s\n", optarg);
276 return 1;
277 }
279 break;
280 default:
281 usage(argv[0]);
282 return 1;
283 }
284 }
285
286 if (!port) {
287 fprintf(stderr, "Error: -p PORT is required\n");
288 usage(argv[0]);
289 return 1;
290 }
291
292 if (mode != MODE_CLIENT) {
294 }
295
296 if (mode == MODE_SERVER) {
297 run_server(port, fake_offset, nb_rounds);
298 } else {
299 run_client(server, port, nb_rounds);
300 }
301
304
305 netw_unload();
306
307 return 0;
308}
static void usage(void)
static void run_server(char *port, double fake_offset, int nb_rounds)
#define MODE_SERVER
#define MSG_SYNC
sync request message: "SYNC client_send_time\n"
static void run_client(char *server, char *port, int nb_rounds)
static double get_time(void)
#define MSG_RESP
sync response message: "RESP client_send_time server_time\n"
#define MODE_CLIENT
int main(void)
int getoptret
Definition ex_fluid.c:60
int log_level
Definition ex_fluid.c:61
static int mode
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:38
NETWORK * server
char * port
double estimated_offset
add to local time to get estimated server time
double estimated_rtt
current estimated round-trip time
N_CLOCK_SYNC * n_clock_sync_new(void)
allocate and initialize a new clock sync estimator
int n_clock_sync_should_send(const N_CLOCK_SYNC *cs, double local_now)
check if it's time to send a new sync request (returns TRUE/FALSE)
double n_clock_sync_server_time(const N_CLOCK_SYNC *cs, double local_now)
get estimated server time given a local time value
void n_clock_sync_delete(N_CLOCK_SYNC **cs)
free a clock sync estimator
void n_clock_sync_mark_sent(N_CLOCK_SYNC *cs, double local_now)
mark that a sync request was just sent
int n_clock_sync_process_response(N_CLOCK_SYNC *cs, double client_send_time, double server_time, double local_now)
record a sync response: client_send_time is the local time the request was sent, server_time is the s...
clock synchronization estimator
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:271
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:88
#define LOG_DEBUG
debug-level messages
Definition n_log.h:83
#define LOG_ERR
error conditions
Definition n_log.h:75
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:120
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:79
#define LOG_INFO
informational
Definition n_log.h:81
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
Definition n_time.c:53
int netw_bind_udp(NETWORK **netw, char *addr, char *port, int ip_version)
Create a UDP bound socket for receiving datagrams.
Definition n_network.c:2360
ssize_t send_udp_data(void *netw, char *buf, uint32_t n)
send data via UDP on a connected socket
Definition n_network.c:2573
ssize_t netw_udp_sendto(NETWORK *netw, char *buf, uint32_t n, struct sockaddr *dest_addr, socklen_t dest_len)
send data via UDP to a specific destination address
Definition n_network.c:2650
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:47
ssize_t recv_udp_data(void *netw, char *buf, uint32_t n)
recv data via UDP from a connected socket
Definition n_network.c:2614
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:2041
int netw_connect_udp(NETWORK **netw, char *host, char *port, int ip_version)
Connect a UDP socket to a remote host.
Definition n_network.c:2468
ssize_t netw_udp_recvfrom(NETWORK *netw, char *buf, uint32_t n, struct sockaddr *src_addr, socklen_t *src_len)
recv data via UDP and capture the source address
Definition n_network.c:2682
Structure of a NETWORK.
Definition n_network.h:258
Clock synchronization estimator for networked games.
Common headers and low-level functions & define.
Generic log system.
Network Engine.