Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_reactor.c

Single-threaded epoll reactor demo (n_reactor + netw_accept_into_reactor)

Single-threaded epoll reactor demo (n_reactor + netw_accept_into_reactor)

Author
Castagnier Mickael
Version
1.0
Date
27/04/2026

Usage: server: ./ex_network_reactor -a [ADDR] -p PORT -n N client: ./ex_network_reactor -s HOST -p PORT -n N

Server mode wires up the n_reactor:

Reactor is Linux/Android only. On other platforms n_reactor_new returns NULL with a LOG_INFO and the example exits 0 (treated as a skip rather than a failure).

/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <pthread.h>
#include "nilorea/n_log.h"
#define MODE_SERVER 0
#define MODE_CLIENT 1
static volatile sig_atomic_t g_running = 1;
static void sighandler(int sig) {
(void)sig;
g_running = 0;
}
static void usage(void) {
fprintf(stderr,
"Usage: ex_network_reactor [options]\n"
" -a ADDR server mode, bind ADDR (use \"\" for all interfaces)\n"
" -s HOST client mode, connect to HOST\n"
" -p PORT port (required)\n"
" -n COUNT server: connections to handle, client: connect attempts (default 5)\n"
" -V LEVEL log level: LOG_DEBUG/LOG_INFO/LOG_NOTICE/LOG_ERR (default LOG_NOTICE)\n"
" -h show this help\n");
}
static int run_server(const char* addr, const char* port, int target) {
NETWORK* listener = NULL;
if (netw_make_listening(&listener, (char*)((addr && addr[0]) ? addr : NULL),
(char*)port, 64, NETWORK_IPALL) == FALSE) {
n_log(LOG_ERR, "netw_make_listening failed on %s:%s", addr ? addr : "*", port);
return 1;
}
n_log(LOG_NOTICE, "reactor server listening on %s:%s (target %d connections)",
addr && addr[0] ? addr : "*", port, target);
/* Reactor + dedicated I/O thread. n_reactor_new returns NULL on
* non-Linux platforms (the public function is still safe to call,
* it just yields a polite no-op so callers don't need #ifdefs). */
n_reactor* reactor = n_reactor_new(0);
if (!reactor) {
n_log(LOG_NOTICE, "n_reactor unavailable on this platform, skipping (exit 0)");
netw_close(&listener);
netw_unload();
return 0;
}
pthread_t reactor_thr;
if (pthread_create(&reactor_thr, NULL, &n_reactor_run_thread_entry, reactor) != 0) {
n_log(LOG_ERR, "pthread_create(reactor): %s", strerror(errno));
n_reactor_destroy(&reactor);
netw_close(&listener);
netw_unload();
return 2;
}
/* Tracking list of active reactor-registered clients. The reactor
* owns I/O; we own the per-connection lifecycle (accept -> echo ->
* close). */
if (!active) {
n_log(LOG_ERR, "new_generic_list failed");
n_reactor_stop(reactor);
pthread_join(reactor_thr, NULL);
n_reactor_destroy(&reactor);
netw_close(&listener);
netw_unload();
return 3;
}
int handled = 0;
while (g_running && handled < target) {
/* Step 1: try to accept (500 ms select timeout, short enough
* that we keep draining recv queues responsively). */
int retval = 0;
NETWORK* client = netw_accept_into_reactor(listener, 0, 0, 500, reactor, &retval);
if (client) {
n_log(LOG_INFO, "accepted client fd=%d (now %d active)",
client->link.sock, (int)(active->nb_items + 1));
list_push(active, client, NULL);
}
/* Step 2: drain any messages the reactor posted onto active
* clients' recv_buf. Echo them back via netw_add_msg, that
* calls n_reactor_notify_send under the hood, waking the
* reactor so the send queue gets flushed. */
LIST_NODE* node = active->start;
while (node) {
LIST_NODE* next = node->next;
NETWORK* c = (NETWORK*)node->ptr;
N_STR* msg = netw_get_msg(c);
if (msg) {
n_log(LOG_INFO, "echoing %zu bytes back to fd=%d",
msg->length, c->link.sock);
if (netw_add_msg(c, msg) != TRUE) {
free_nstr(&msg);
}
/* Give the reactor a brief window to flush the echo
* before the close handshake severs SHUT_WR. */
u_sleep(20000);
/* netw_close calls n_reactor_close_netw_sync internally
* because c->reactor_mode is set, no manual unregister
* needed. */
/* remove_list_node_f unlinks `node`, frees the
* LIST_NODE struct, and returns the void* it held
* (which we already netw_close'd). */
(void)remove_list_node_f(active, node);
handled++;
n_log(LOG_NOTICE, "handled %d/%d connections", handled, target);
}
node = next;
}
}
/* Anything still registered didn't get its echo before the target
* was reached or SIGINT fired. Close them cleanly. */
LIST_NODE* node = active->start;
while (node) {
LIST_NODE* next = node->next;
NETWORK* c = (NETWORK*)node->ptr;
(void)remove_list_node_f(active, node);
node = next;
}
list_destroy(&active);
n_reactor_get_stats(reactor, &stats);
"reactor stats: events=%lld registered=%lld unregistered=%lld "
"wake=%lld writes_partial=%lld reads_partial=%lld",
n_reactor_stop(reactor);
pthread_join(reactor_thr, NULL);
n_reactor_destroy(&reactor);
netw_close(&listener);
netw_unload();
n_log(LOG_NOTICE, "reactor server done (%d connections handled)", handled);
return 0;
}
static int run_client(const char* host, const char* port, int attempts) {
int rc = 0;
for (int i = 0; g_running && i < attempts; i++) {
NETWORK* netw = NULL;
if (netw_connect(&netw, (char*)host, (char*)port, NETWORK_IPALL) != TRUE) {
n_log(LOG_ERR, "client connect %d/%d to %s:%s failed", i + 1, attempts, host, port);
rc = 4;
continue;
}
char payload[64];
snprintf(payload, sizeof(payload), "hello-from-client-%d", i + 1);
N_STR* out = char_to_nstr(payload);
if (netw_add_msg(netw, out) != TRUE) {
free_nstr(&out);
}
N_STR* in = netw_wait_msg(netw, 25000, 5000000);
if (in) {
n_log(LOG_NOTICE, "client %d: echo received (%zu bytes)", i + 1, in->length);
free_nstr(&in);
} else {
n_log(LOG_ERR, "client %d: no echo within timeout", i + 1);
rc = 5;
}
}
netw_unload();
return rc;
}
int main(int argc, char** argv) {
char* addr = NULL;
char* host = NULL;
char* port = NULL;
int count = 5;
int explicit_server = 0;
int opt;
while ((opt = getopt(argc, argv, "ha:s:p:n:V:")) != -1) {
switch (opt) {
case 'a':
explicit_server = 1;
addr = strdup(optarg);
break;
case 's':
host = strdup(optarg);
break;
case 'p':
port = strdup(optarg);
break;
case 'n':
count = atoi(optarg);
if (count <= 0) count = 1;
break;
case 'V':
if (!strcmp(optarg, "LOG_DEBUG"))
else if (!strcmp(optarg, "LOG_INFO"))
else if (!strcmp(optarg, "LOG_NOTICE"))
else if (!strcmp(optarg, "LOG_ERR"))
break;
case 'h':
default:
usage();
FreeNoLog(host);
return 1;
}
}
(void)explicit_server;
if (!port) {
fprintf(stderr, "ex_network_reactor: -p PORT is required\n");
usage();
FreeNoLog(host);
return 1;
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
#ifdef __linux__
signal(SIGPIPE, SIG_IGN);
#endif
int rc;
if (mode == MODE_CLIENT) {
rc = run_client(host, port, count);
} else {
rc = run_server(addr, port, count);
}
FreeNoLog(host);
return rc;
}
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