Clock synchronization demo using UDP networking.
The client periodically sends sync requests containing its local timestamp. The server echoes back the client timestamp along with its own timestamp. The client feeds these into N_CLOCK_SYNC to compute the median offset and RTT.
Usage: Server: ./ex_clock_sync -p 12345 Client: ./ex_clock_sync -s 127.0.0.1 -p 12345
Use -o OFFSET on the server to simulate a clock offset (seconds).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#define MODE_SERVER 0
#define MODE_CLIENT 1
#define MSG_SYNC "SYNC"
#define MSG_RESP "RESP"
static void usage(
const char* prog) {
fprintf(stderr,
"Usage:\n"
" Server: %s -p PORT [-o OFFSET] [-V LOGLEVEL]\n"
" Client: %s -s ADDRESS -p PORT [-n COUNT] [-V LOGLEVEL]\n"
"\n"
"Options:\n"
" -p PORT port to use\n"
" -s ADDRESS server address (client mode)\n"
" -o OFFSET simulated clock offset in seconds (server only, default: 0.42)\n"
" -n COUNT number of sync rounds (client only, default: 20)\n"
" -V LEVEL log level: LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_ERR\n"
" -h show this help\n",
prog, prog);
}
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}
static void run_server(
char*
port,
double fake_offset,
int nb_rounds) {
n_log(
LOG_NOTICE,
"Server: binding UDP on port %s (simulated offset: %.4f s)",
port, fake_offset);
return;
}
for (int i = 0; i < nb_rounds; i++) {
char buf[256] = "";
struct sockaddr_storage client_addr;
socklen_t client_len = sizeof(client_addr);
(struct sockaddr*)&client_addr, &client_len);
if (received <= 0) {
continue;
}
buf[received] = '\0';
double client_send_time = 0.0;
if (sscanf(buf,
MSG_SYNC " %lf", &client_send_time) != 1) {
continue;
}
double server_time =
get_time() + fake_offset;
char resp[256];
snprintf(resp,
sizeof(resp),
MSG_RESP " %.9f %.9f", client_send_time, server_time);
(struct sockaddr*)&client_addr, client_len);
if (sent <= 0) {
} else {
n_log(
LOG_DEBUG,
"Server: responded to sync #%d (server_time=%.4f)", i + 1, server_time);
}
}
}
return;
}
if (!cs) {
n_log(
LOG_ERR,
"Client: failed to create clock sync estimator");
return;
}
n_log(
LOG_NOTICE,
" Round | RTT (ms) | Offset (ms) | Est.Offset (ms) | Est.RTT (ms)");
n_log(
LOG_NOTICE,
"-------+-----------+-------------+-----------------+-------------");
int rounds_done = 0;
while (rounds_done < nb_rounds) {
continue;
}
char msg[256];
snprintf(msg,
sizeof(msg),
MSG_SYNC " %.9f", send_time);
if (sent <= 0) {
break;
}
char buf[256] = "";
if (received <= 0) {
rounds_done++;
continue;
}
buf[received] = '\0';
double resp_client_time = 0.0, resp_server_time = 0.0;
if (sscanf(buf,
MSG_RESP " %lf %lf", &resp_client_time, &resp_server_time) != 2) {
rounds_done++;
continue;
}
rounds_done++;
double raw_rtt = (recv_time - resp_client_time) * 1000.0;
double raw_offset = (resp_server_time + (recv_time - resp_client_time) / 2.0 - recv_time) * 1000.0;
rounds_done,
raw_rtt,
raw_offset,
}
n_log(
LOG_NOTICE,
"-------+-----------+-------------+-----------------+-------------");
n_log(
LOG_NOTICE,
"Difference: %.4f ms", (est_server_now - local_now) * 1000.0);
}
int main(
int argc,
char* argv[]) {
int nb_rounds = 20;
double fake_offset = 0.42;
if (argc == 1) {
return 1;
}
while ((
getoptret = getopt(argc, argv,
"hs:p:n:o:V:")) != EOF) {
case 'h':
return 0;
case 's':
break;
case 'p':
break;
case 'n':
nb_rounds = atoi(optarg);
if (nb_rounds < 1) nb_rounds = 1;
break;
case 'o':
fake_offset = atof(optarg);
break;
case 'V':
if (!strncmp("LOG_DEBUG", optarg, 9)) {
} else if (!strncmp("LOG_INFO", optarg, 8)) {
} else if (!strncmp("LOG_NOTICE", optarg, 10)) {
} else if (!strncmp("LOG_ERR", optarg, 7)) {
} else {
fprintf(stderr, "Unknown log level: %s\n", optarg);
return 1;
}
break;
default:
return 1;
}
}
fprintf(stderr, "Error: -p PORT is required\n");
return 1;
}
}
} else {
}
netw_unload();
return 0;
}
static void run_server(char *port, double fake_offset, int nb_rounds)
#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"
NETWORK * netw
Network for server mode, accepting incomming.
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.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
#define LOG_NOTICE
normal but significant condition
#define LOG_INFO
informational
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
int netw_bind_udp(NETWORK **netw, char *addr, char *port, int ip_version)
Create a UDP bound socket for receiving datagrams.
ssize_t send_udp_data(void *netw, char *buf, uint32_t n)
send data via UDP on a connected socket
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
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
ssize_t recv_udp_data(void *netw, char *buf, uint32_t n)
recv data via UDP from a connected socket
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
int netw_connect_udp(NETWORK **netw, char *host, char *port, int ip_version)
Connect a UDP socket to a remote host.
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
Clock synchronization estimator for networked games.
Common headers and low-level functions & define.