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 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <errno.h>
46#include <getopt.h>
47
48#include "nilorea/n_common.h"
49#include "nilorea/n_log.h"
50#include "nilorea/n_network.h"
52
53#define MODE_SERVER 0
54#define MODE_CLIENT 1
55
57#define MSG_SYNC "SYNC"
59#define MSG_RESP "RESP"
60
61static void usage(const char* prog) {
62 fprintf(stderr,
63 "Usage:\n"
64 " Server: %s -p PORT [-o OFFSET] [-V LOGLEVEL]\n"
65 " Client: %s -s ADDRESS -p PORT [-n COUNT] [-V LOGLEVEL]\n"
66 "\n"
67 "Options:\n"
68 " -p PORT port to use\n"
69 " -s ADDRESS server address (client mode)\n"
70 " -o OFFSET simulated clock offset in seconds (server only, default: 0.42)\n"
71 " -n COUNT number of sync rounds (client only, default: 20)\n"
72 " -V LEVEL log level: LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_ERR\n"
73 " -h show this help\n",
74 prog, prog);
75}
76
77static double get_time(void) {
78 struct timespec ts;
79 clock_gettime(CLOCK_MONOTONIC, &ts);
80 return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
81}
82
83static void run_server(char* port, double fake_offset, int nb_rounds) {
84 NETWORK* netw = NULL;
85
86 n_log(LOG_NOTICE, "Server: binding UDP on port %s (simulated offset: %.4f s)", port, fake_offset);
87
88 if (netw_bind_udp(&netw, NULL, port, NETWORK_IPALL) == FALSE) {
89 n_log(LOG_ERR, "Server: failed to bind UDP on port %s", port);
90 return;
91 }
92
93 n_log(LOG_NOTICE, "Server: listening for sync requests...");
94
95 for (int i = 0; i < nb_rounds; i++) {
96 char buf[256] = "";
97 struct sockaddr_storage client_addr;
98 socklen_t client_len = sizeof(client_addr);
99
100 ssize_t received = netw_udp_recvfrom(netw, buf, sizeof(buf) - 1,
101 (struct sockaddr*)&client_addr, &client_len);
102 if (received <= 0) {
103 n_log(LOG_ERR, "Server: recv error");
104 continue;
105 }
106 buf[received] = '\0';
107
108 /* parse: "SYNC client_send_time" */
109 double client_send_time = 0.0;
110 if (sscanf(buf, MSG_SYNC " %lf", &client_send_time) != 1) {
111 n_log(LOG_ERR, "Server: malformed request: %s", buf);
112 continue;
113 }
114
115 /* server time = local time + fake offset to simulate a different clock */
116 double server_time = get_time() + fake_offset;
117
118 /* respond: "RESP client_send_time server_time" */
119 char resp[256];
120 snprintf(resp, sizeof(resp), MSG_RESP " %.9f %.9f", client_send_time, server_time);
121
122 ssize_t sent = netw_udp_sendto(netw, resp, (uint32_t)strlen(resp),
123 (struct sockaddr*)&client_addr, client_len);
124 if (sent <= 0) {
125 n_log(LOG_ERR, "Server: send error");
126 } else {
127 n_log(LOG_DEBUG, "Server: responded to sync #%d (server_time=%.4f)", i + 1, server_time);
128 }
129 }
130
131 n_log(LOG_NOTICE, "Server: done after %d rounds", nb_rounds);
133}
134
135static void run_client(char* server, char* port, int nb_rounds) {
136 NETWORK* netw = NULL;
137
138 n_log(LOG_NOTICE, "Client: connecting to %s:%s for %d sync rounds", server, port, nb_rounds);
139
140 if (netw_connect_udp(&netw, server, port, NETWORK_IPALL) != TRUE) {
141 n_log(LOG_ERR, "Client: failed to connect UDP to %s:%s", server, port);
142 return;
143 }
144
146 if (!cs) {
147 n_log(LOG_ERR, "Client: failed to create clock sync estimator");
149 return;
150 }
151
152 n_log(LOG_NOTICE, "Client: starting clock synchronization...");
153 n_log(LOG_NOTICE, "");
154 n_log(LOG_NOTICE, " Round | RTT (ms) | Offset (ms) | Est.Offset (ms) | Est.RTT (ms)");
155 n_log(LOG_NOTICE, "-------+-----------+-------------+-----------------+-------------");
156
157 int rounds_done = 0;
158 while (rounds_done < nb_rounds) {
159 double now = get_time();
160
161 if (!n_clock_sync_should_send(cs, now)) {
162 u_sleep(50000); /* 50ms poll */
163 continue;
164 }
165
166 /* send sync request */
167 double send_time = get_time();
168 n_clock_sync_mark_sent(cs, send_time);
169
170 char msg[256];
171 snprintf(msg, sizeof(msg), MSG_SYNC " %.9f", send_time);
172 ssize_t sent = send_udp_data(netw, msg, (uint32_t)strlen(msg));
173 if (sent <= 0) {
174 n_log(LOG_ERR, "Client: send error");
175 break;
176 }
177
178 /* wait for response */
179 char buf[256] = "";
180 ssize_t received = recv_udp_data(netw, buf, sizeof(buf) - 1);
181 if (received <= 0) {
182 n_log(LOG_ERR, "Client: recv error or timeout");
183 rounds_done++;
184 continue;
185 }
186 buf[received] = '\0';
187
188 double recv_time = get_time();
189
190 /* parse: "RESP client_send_time server_time" */
191 double resp_client_time = 0.0, resp_server_time = 0.0;
192 if (sscanf(buf, MSG_RESP " %lf %lf", &resp_client_time, &resp_server_time) != 2) {
193 n_log(LOG_ERR, "Client: malformed response: %s", buf);
194 rounds_done++;
195 continue;
196 }
197
198 /* feed the sample into the estimator */
199 n_clock_sync_process_response(cs, resp_client_time, resp_server_time, recv_time);
200
201 rounds_done++;
202
203 double raw_rtt = (recv_time - resp_client_time) * 1000.0;
204 double raw_offset = (resp_server_time + (recv_time - resp_client_time) / 2.0 - recv_time) * 1000.0;
205
206 n_log(LOG_NOTICE, " %5d | %9.3f | %11.3f | %15.3f | %11.3f",
207 rounds_done,
208 raw_rtt,
209 raw_offset,
210 cs->estimated_offset * 1000.0,
211 cs->estimated_rtt * 1000.0);
212 }
213
214 n_log(LOG_NOTICE, "-------+-----------+-------------+-----------------+-------------");
215 n_log(LOG_NOTICE, "");
216 n_log(LOG_NOTICE, "Final estimated offset: %.4f ms (%.6f s)", cs->estimated_offset * 1000.0, cs->estimated_offset);
217 n_log(LOG_NOTICE, "Final estimated RTT: %.4f ms (%.6f s)", cs->estimated_rtt * 1000.0, cs->estimated_rtt);
218 n_log(LOG_NOTICE, "");
219
220 /* demonstrate n_clock_sync_server_time */
221 double local_now = get_time();
222 double est_server_now = n_clock_sync_server_time(cs, local_now);
223 n_log(LOG_NOTICE, "Local time: %.6f", local_now);
224 n_log(LOG_NOTICE, "Estimated server time: %.6f", est_server_now);
225 n_log(LOG_NOTICE, "Difference: %.4f ms", (est_server_now - local_now) * 1000.0);
226
229}
230
231int main(int argc, char* argv[]) {
232 int mode = -1;
233 char* server = NULL;
234 char* port = NULL;
235 int nb_rounds = 20;
236 double fake_offset = 0.42;
237 int log_level = LOG_NOTICE;
238 int getoptret = 0;
239
241
242 if (argc == 1) {
243 usage(argv[0]);
244 return 1;
245 }
246
247 while ((getoptret = getopt(argc, argv, "hs:p:n:o:V:")) != EOF) {
248 switch (getoptret) {
249 case 'h':
250 usage(argv[0]);
251 return 0;
252 case 's':
253 server = strdup(optarg);
255 break;
256 case 'p':
257 port = strdup(optarg);
258 break;
259 case 'n':
260 nb_rounds = atoi(optarg);
261 if (nb_rounds < 1) nb_rounds = 1;
262 break;
263 case 'o':
264 fake_offset = atof(optarg);
265 break;
266 case 'V':
267 if (!strncmp("LOG_DEBUG", optarg, 9)) {
269 } else if (!strncmp("LOG_INFO", optarg, 8)) {
271 } else if (!strncmp("LOG_NOTICE", optarg, 10)) {
273 } else if (!strncmp("LOG_ERR", optarg, 7)) {
275 } else {
276 fprintf(stderr, "Unknown log level: %s\n", optarg);
277 return 1;
278 }
280 break;
281 default:
282 usage(argv[0]);
283 return 1;
284 }
285 }
286
287 if (!port) {
288 fprintf(stderr, "Error: -p PORT is required\n");
289 usage(argv[0]);
290 return 1;
291 }
292
293 if (mode != MODE_CLIENT) {
295 }
296
297 if (mode == MODE_SERVER) {
298 run_server(port, fake_offset, nb_rounds);
299 } else {
300 run_client(server, port, nb_rounds);
301 }
302
305
306 netw_unload();
307
308 return 0;
309}
static void usage(void)
int main(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 getoptret
Definition ex_fluid.c:59
int log_level
Definition ex_fluid.c:60
static int mode
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:39
NETWORK * server
char * port
double estimated_offset
add to local time to get estimated server time (raw best-RTT target — n_clock_sync_server_time applie...
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:272
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_INFO
informational
Definition n_log.h:82
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
Definition n_time.c:54
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:3005
ssize_t send_udp_data(void *netw, char *buf, uint32_t n)
send data via UDP on a connected socket
Definition n_network.c:3218
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:3295
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
ssize_t recv_udp_data(void *netw, char *buf, uint32_t n)
recv data via UDP from a connected socket
Definition n_network.c:3259
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:2662
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:3113
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:3327
Structure of a NETWORK.
Definition n_network.h:309
Clock synchronization estimator for networked games.
Common headers and low-level functions & define.
Generic log system.
Network Engine.