Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_reactor.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
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <errno.h>
54#include <getopt.h>
55#include <signal.h>
56#include <pthread.h>
57
58#include "nilorea/n_common.h"
59#include "nilorea/n_log.h"
60#include "nilorea/n_network.h"
61#include "nilorea/n_reactor.h"
62
63#define MODE_SERVER 0
64#define MODE_CLIENT 1
65
66static volatile sig_atomic_t g_running = 1;
67
68static void sighandler(int sig) {
69 (void)sig;
70 g_running = 0;
71}
72
73static void usage(void) {
74 fprintf(stderr,
75 "Usage: ex_network_reactor [options]\n"
76 " -a ADDR server mode, bind ADDR (use \"\" for all interfaces)\n"
77 " -s HOST client mode, connect to HOST\n"
78 " -p PORT port (required)\n"
79 " -n COUNT server: connections to handle, client: connect attempts (default 5)\n"
80 " -V LEVEL log level: LOG_DEBUG/LOG_INFO/LOG_NOTICE/LOG_ERR (default LOG_NOTICE)\n"
81 " -h show this help\n");
82}
83
94static int run_server(const char* addr, const char* port, int target) {
95 NETWORK* listener = NULL;
96 if (netw_make_listening(&listener, (char*)((addr && addr[0]) ? addr : NULL),
97 (char*)port, 64, NETWORK_IPALL) == FALSE) {
98 n_log(LOG_ERR, "netw_make_listening failed on %s:%s", addr ? addr : "*", port);
99 return 1;
100 }
101 n_log(LOG_NOTICE, "reactor server listening on %s:%s (target %d connections)",
102 addr && addr[0] ? addr : "*", port, target);
103
104 /* Reactor + dedicated I/O thread. n_reactor_new returns NULL on
105 * non-Linux platforms (the public function is still safe to call,
106 * it just yields a polite no-op so callers don't need #ifdefs). */
107 n_reactor* reactor = n_reactor_new(0);
108 if (!reactor) {
109 n_log(LOG_NOTICE, "n_reactor unavailable on this platform, skipping (exit 0)");
110 netw_close(&listener);
111 netw_unload();
112 return 0;
113 }
114
115 pthread_t reactor_thr;
116 if (pthread_create(&reactor_thr, NULL, &n_reactor_run_thread_entry, reactor) != 0) {
117 n_log(LOG_ERR, "pthread_create(reactor): %s", strerror(errno));
118 n_reactor_destroy(&reactor);
119 netw_close(&listener);
120 netw_unload();
121 return 2;
122 }
123
124 /* Tracking list of active reactor-registered clients. The reactor
125 * owns I/O; we own the per-connection lifecycle (accept -> echo ->
126 * close). */
128 if (!active) {
129 n_log(LOG_ERR, "new_generic_list failed");
130 n_reactor_stop(reactor);
131 pthread_join(reactor_thr, NULL);
132 n_reactor_destroy(&reactor);
133 netw_close(&listener);
134 netw_unload();
135 return 3;
136 }
137
138 int handled = 0;
139 while (g_running && handled < target) {
140 /* Step 1: try to accept (500 ms select timeout, short enough
141 * that we keep draining recv queues responsively). */
142 int retval = 0;
143 NETWORK* client = netw_accept_into_reactor(listener, 0, 0, 500, reactor, &retval);
144 if (client) {
145 n_log(LOG_INFO, "accepted client fd=%d (now %d active)",
146 client->link.sock, (int)(active->nb_items + 1));
147 list_push(active, client, NULL);
148 }
149
150 /* Step 2: drain any messages the reactor posted onto active
151 * clients' recv_buf. Echo them back via netw_add_msg, that
152 * calls n_reactor_notify_send under the hood, waking the
153 * reactor so the send queue gets flushed. */
154 LIST_NODE* node = active->start;
155 while (node) {
156 LIST_NODE* next = node->next;
157 NETWORK* c = (NETWORK*)node->ptr;
158 N_STR* msg = netw_get_msg(c);
159 if (msg) {
160 n_log(LOG_INFO, "echoing %zu bytes back to fd=%d",
161 msg->length, c->link.sock);
162 if (netw_add_msg(c, msg) != TRUE) {
163 free_nstr(&msg);
164 }
165 /* Give the reactor a brief window to flush the echo
166 * before the close handshake severs SHUT_WR. */
167 u_sleep(20000);
168 /* netw_close calls n_reactor_close_netw_sync internally
169 * because c->reactor_mode is set, no manual unregister
170 * needed. */
171 netw_close(&c);
172 /* remove_list_node_f unlinks `node`, frees the
173 * LIST_NODE struct, and returns the void* it held
174 * (which we already netw_close'd). */
175 (void)remove_list_node_f(active, node);
176 handled++;
177 n_log(LOG_NOTICE, "handled %d/%d connections", handled, target);
178 }
179 node = next;
180 }
181 }
182
183 /* Anything still registered didn't get its echo before the target
184 * was reached or SIGINT fired. Close them cleanly. */
185 LIST_NODE* node = active->start;
186 while (node) {
187 LIST_NODE* next = node->next;
188 NETWORK* c = (NETWORK*)node->ptr;
189 netw_close(&c);
190 (void)remove_list_node_f(active, node);
191 node = next;
192 }
193 list_destroy(&active);
194
195 n_reactor_stats stats;
196 n_reactor_get_stats(reactor, &stats);
198 "reactor stats: events=%lld registered=%lld unregistered=%lld "
199 "wake=%lld writes_partial=%lld reads_partial=%lld",
201 stats.wake_signals, stats.writes_partial, stats.reads_partial);
202
203 n_reactor_stop(reactor);
204 pthread_join(reactor_thr, NULL);
205 n_reactor_destroy(&reactor);
206
207 netw_close(&listener);
208 netw_unload();
209 n_log(LOG_NOTICE, "reactor server done (%d connections handled)", handled);
210 return 0;
211}
212
222static int run_client(const char* host, const char* port, int attempts) {
223 int rc = 0;
224 for (int i = 0; g_running && i < attempts; i++) {
225 NETWORK* netw = NULL;
226 if (netw_connect(&netw, (char*)host, (char*)port, NETWORK_IPALL) != TRUE) {
227 n_log(LOG_ERR, "client connect %d/%d to %s:%s failed", i + 1, attempts, host, port);
228 rc = 4;
229 continue;
230 }
232
233 char payload[64];
234 snprintf(payload, sizeof(payload), "hello-from-client-%d", i + 1);
235 N_STR* out = char_to_nstr(payload);
236 if (netw_add_msg(netw, out) != TRUE) {
237 free_nstr(&out);
238 }
239
240 N_STR* in = netw_wait_msg(netw, 25000, 5000000);
241 if (in) {
242 n_log(LOG_NOTICE, "client %d: echo received (%zu bytes)", i + 1, in->length);
243 free_nstr(&in);
244 } else {
245 n_log(LOG_ERR, "client %d: no echo within timeout", i + 1);
246 rc = 5;
247 }
249 }
250 netw_unload();
251 return rc;
252}
253
254int main(int argc, char** argv) {
255 char* addr = NULL;
256 char* host = NULL;
257 char* port = NULL;
258 int mode = MODE_SERVER;
259 int count = 5;
260 int log_level = LOG_NOTICE;
261 int explicit_server = 0;
262 int opt;
263
264 while ((opt = getopt(argc, argv, "ha:s:p:n:V:")) != -1) {
265 switch (opt) {
266 case 'a':
268 explicit_server = 1;
269 addr = strdup(optarg);
270 break;
271 case 's':
273 host = strdup(optarg);
274 break;
275 case 'p':
276 port = strdup(optarg);
277 break;
278 case 'n':
279 count = atoi(optarg);
280 if (count <= 0) count = 1;
281 break;
282 case 'V':
283 if (!strcmp(optarg, "LOG_DEBUG"))
285 else if (!strcmp(optarg, "LOG_INFO"))
287 else if (!strcmp(optarg, "LOG_NOTICE"))
289 else if (!strcmp(optarg, "LOG_ERR"))
291 break;
292 case 'h':
293 default:
294 usage();
296 FreeNoLog(host);
298 return 1;
299 }
300 }
301 (void)explicit_server;
302
303 if (!port) {
304 fprintf(stderr, "ex_network_reactor: -p PORT is required\n");
305 usage();
307 FreeNoLog(host);
308 return 1;
309 }
310
312 signal(SIGINT, sighandler);
313 signal(SIGTERM, sighandler);
314#ifdef __linux__
315 signal(SIGPIPE, SIG_IGN);
316#endif
317
318 int rc;
319 if (mode == MODE_CLIENT) {
320 rc = run_client(host, port, count);
321 } else {
322 rc = run_server(addr, port, count);
323 }
324
326 FreeNoLog(host);
328 return rc;
329}
static void usage(void)
static void sighandler(int sig)
int main(void)
static void run_server(char *port, double fake_offset, int nb_rounds)
#define MODE_SERVER
static void run_client(char *server, char *port, int nb_rounds)
#define MODE_CLIENT
int log_level
Definition ex_fluid.c:60
static int mode
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:39
static volatile sig_atomic_t g_running
char * addr
char * port
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:272
void * ptr
void pointer to store
Definition n_list.h:46
LIST_NODE * start
pointer to the start of the list
Definition n_list.h:66
size_t nb_items
number of item currently in the list
Definition n_list.h:61
struct LIST_NODE * next
pointer to the next node
Definition n_list.h:52
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
Definition n_list.c:228
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:548
void * remove_list_node_f(LIST *list, LIST_NODE *node)
Internal function called each time we need to get a node out of a list.
Definition n_list.c:76
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
Definition n_list.c:37
#define MAX_LIST_ITEMS
flag to pass to new_generic_list for the maximum possible number of item in a list
Definition n_list.h:75
Structure of a generic LIST container.
Definition n_list.h:59
Structure of a generic list node.
Definition n_list.h:44
#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
size_t length
total allocation (in bytes) of the data buffer, padding included
Definition n_str.h:65
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
Definition n_str.c:255
A box including a string and his lenght.
Definition n_str.h:61
void u_sleep(unsigned int usec)
wrapper around usleep for API consistency
Definition n_time.c:54
N_SOCKET link
networking socket
Definition n_network.h:388
SOCKET sock
a normal socket
Definition n_network.h:293
N_STR * netw_get_msg(NETWORK *netw)
Get a message from aimed NETWORK.
Definition n_network.c:3666
int netw_add_msg(NETWORK *netw, N_STR *msg)
Add a message to send in aimed NETWORK.
Definition n_network.c:3569
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:2885
int netw_start_thr_engine(NETWORK *netw)
Start the NETWORK netw Threaded Engine.
Definition n_network.c:3740
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:48
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:2662
N_STR * netw_wait_msg(NETWORK *netw, unsigned int refresh, size_t timeout)
Wait a message from aimed NETWORK.
Definition n_network.c:3688
int netw_connect(NETWORK **netw, char *host, char *port, int ip_version)
Use this to connect a NETWORK to any listening one, unrestricted send/recv lists.
Definition n_network.c:2359
Structure of a NETWORK.
Definition n_network.h:309
Common headers and low-level functions & define.
Generic log system.
Network Engine.
void * n_reactor_run_thread_entry(void *arg)
pthread_create-compatible entry point that calls n_reactor_run on the reactor passed via arg.
Definition n_reactor.c:1227
void n_reactor_get_stats(const n_reactor *reactor, n_reactor_stats *out)
Read current stats counters into *out.
Definition n_reactor.c:1232
n_reactor * n_reactor_new(int max_fds_hint)
Create a new reactor.
Definition n_reactor.c:1207
NETWORK * netw_accept_into_reactor(NETWORK *listener, size_t send_list_limit, size_t recv_list_limit, int blocking, n_reactor *reactor, int *retval)
Accept a connection on listener and register it with reactor instead of starting per-connection threa...
Definition n_reactor.c:1256
void n_reactor_stop(n_reactor *reactor)
Signal the run loop to exit at the next iteration.
Definition n_reactor.c:1223
void n_reactor_destroy(n_reactor **reactor)
Tear down a reactor.
Definition n_reactor.c:1215
Single-threaded epoll reactor for n_network connections.
long long fds_registered
lifetime register call count
Definition n_reactor.h:82
long long writes_partial
EAGAIN on send -> re-armed EPOLLOUT.
Definition n_reactor.h:80
long long reads_partial
EAGAIN on recv -> kept accumulator.
Definition n_reactor.h:81
long long events_processed
total epoll events dispatched
Definition n_reactor.h:79
long long wake_signals
eventfd wake events processed
Definition n_reactor.h:84
struct n_reactor n_reactor
Opaque reactor handle.
Definition n_reactor.h:74
long long fds_unregistered
lifetime unregister call count
Definition n_reactor.h:83
Counters for the dashboard / profile_server.sh.
Definition n_reactor.h:78