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

GUI network chat demo using the n_gui and n_network modules.

GUI network chat demo using the n_gui and n_network modules. A simple TCP chat application with an Allegro5 GUI front-end. Run as server: ex_gui_network -p 8080 Run as client: ex_gui_network -s 127.0.0.1 -p 8080

Demonstrates:

Author
Castagnier Mickael
Version
1.0
Date
06/03/2026
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#define WIDTH 800
#define HEIGHT 600
#define ALLEGRO_UNSTABLE 1
#include <getopt.h>
#include <sys/time.h>
#include <sys/types.h>
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"
#include "nilorea/n_time.h"
#include "nilorea/n_gui.h"
/* custom message type for chat text */
#define NETMSG_CHAT 100
ALLEGRO_DISPLAY* display = NULL;
static int DONE = 0;
static int mode = -1; /* 0 = server, 1 = client */
static char* server_addr = NULL;
static char* port_str = NULL;
/* networking */
static NETWORK* netw_server = NULL;
static NETWORK* netw_client = NULL;
static NETWORK_POOL* pool = NULL;
/* gui widget ids */
static int win_chat = -1;
static int lbl_status = -1;
static int listbox_log = -1;
static int textarea_input = -1;
static int btn_send = -1;
/* server-only gui widget ids */
static int win_clients = -1;
static int lbl_clients_header = -1;
static int listbox_clients = -1;
static N_GUI_CTX* gui = NULL;
void chat_log_add(const char* text) {
if (gui && listbox_log >= 0) {
}
}
if (!gui || listbox_clients < 0 || !pool) return;
HT_FOREACH(node, pool->pool, {
NETWORK* cn = (NETWORK*)node->data.ptr;
if (cn) {
char entry[256] = "";
snprintf(entry, sizeof(entry), "%s:%s (sock %d)",
cn->link.ip ? cn->link.ip : "?",
cn->link.port ? cn->link.port : "?",
(int)cn->link.sock);
n_gui_listbox_add_item(gui, listbox_clients, entry);
}
});
char header[64] = "";
snprintf(header, sizeof(header), "Connected clients: %zu", netw_pool_nbclients(pool));
if (lbl_clients_header >= 0) {
}
}
N_STR* build_chat_msg(const char* text) {
NETW_MSG* msg = NULL;
create_msg(&msg);
if (!msg) return NULL;
add_strdup_to_msg(msg, text);
N_STR* str = make_str_from_msg(msg);
delete_msg(&msg);
return str;
}
int decode_chat_msg(N_STR* str, char** out_text) {
NETW_MSG* msg = NULL;
int type = 0;
int ok = FALSE;
if (out_text) {
*out_text = NULL;
}
msg = make_msg_from_str(str);
if (!msg) {
return FALSE;
}
if (!get_int_from_msg(msg, &type)) {
delete_msg(&msg);
return FALSE;
}
if (type != NETMSG_CHAT) {
delete_msg(&msg);
return FALSE;
}
ok = get_str_from_msg(msg, out_text);
if (!ok && out_text) {
*out_text = NULL;
}
delete_msg(&msg);
return ok;
}
void on_send_click(int widget_id, void* user_data) {
(void)widget_id;
(void)user_data;
if (!text || text[0] == '\0') return;
N_STR* msg_str = build_chat_msg(text);
if (!msg_str) return;
if (mode == 0 && pool) {
/* server: broadcast to all clients */
netw_pool_broadcast(pool, NULL, msg_str);
char logbuf[512] = "";
snprintf(logbuf, sizeof(logbuf), "[server] %s", text);
chat_log_add(logbuf);
} else if (mode == 1 && netw_client) {
N_STR* copy = nstrdup(msg_str);
if (!netw_add_msg(netw_client, copy)) {
/* netw_add_msg did not take ownership of 'copy' on failure */
free_nstr(&copy);
}
char logbuf[512] = "";
snprintf(logbuf, sizeof(logbuf), "[me] %s", text);
chat_log_add(logbuf);
}
free_nstr(&msg_str);
}
void poll_network_msgs(NETWORK* netw, const char* prefix) {
if (!netw) return;
N_STR* incoming = NULL;
while ((incoming = netw_get_msg(netw)) != NULL) {
char* text = NULL;
if (decode_chat_msg(incoming, &text) == TRUE && text) {
char logbuf[512] = "";
snprintf(logbuf, sizeof(logbuf), "[%s] %s", prefix, text);
chat_log_add(logbuf);
/* server: relay to all other clients */
if (mode == 0 && pool) {
N_STR* relay = build_chat_msg(text);
if (relay) {
free_nstr(&relay);
}
}
Free(text);
}
free_nstr(&incoming);
}
}
void usage(void) {
fprintf(stderr,
"Usage:\n"
" Server: ex_gui_network -p PORT\n"
" Client: ex_gui_network -s ADDRESS -p PORT\n"
" Options:\n"
" -s addr : server address to connect to (client mode)\n"
" -p port : port number\n"
" -V level : log level (LOG_DEBUG, LOG_INFO, LOG_ERR)\n"
" -h : help\n");
}
int main(int argc, char** argv) {
int getoptret = 0;
while ((getoptret = getopt(argc, argv, "hs:p:V:")) != EOF) {
switch (getoptret) {
case 's':
server_addr = strdup(optarg);
break;
case 'p':
port_str = strdup(optarg);
break;
case 'V':
if (!strcmp("LOG_DEBUG", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_ERR", optarg))
break;
default:
case 'h':
usage();
exit(1);
}
}
if (!port_str) {
fprintf(stderr, "Port is required. Use -p PORT\n");
usage();
exit(1);
}
mode = server_addr ? 1 : 0;
/* allegro init */
if (!al_init()) {
n_log(LOG_ERR, "al_init failed");
return 1;
}
al_install_keyboard();
al_install_mouse();
al_init_primitives_addon();
al_init_font_addon();
al_init_ttf_addon();
al_init_image_addon();
al_set_new_display_flags(ALLEGRO_OPENGL | ALLEGRO_WINDOWED);
display = al_create_display(WIDTH, HEIGHT);
if (!display) {
n_log(LOG_ERR, "Failed to create display");
return 1;
}
al_set_window_title(display, mode == 0 ? "GUI Network - Server" : "GUI Network - Client");
ALLEGRO_FONT* font = al_create_builtin_font();
ALLEGRO_EVENT_QUEUE* event_queue = al_create_event_queue();
ALLEGRO_TIMER* timer = al_create_timer(1.0 / 30.0);
al_register_event_source(event_queue, al_get_display_event_source(display));
al_register_event_source(event_queue, al_get_keyboard_event_source());
al_register_event_source(event_queue, al_get_mouse_event_source());
al_register_event_source(event_queue, al_get_timer_event_source(timer));
/* create GUI */
gui = n_gui_new_ctx(font);
/* main chat window */
win_chat = n_gui_add_window(gui, mode == 0 ? "Chat Server" : "Chat Client", 10, 10, 780, 580);
/* status label */
lbl_status = n_gui_add_label(gui, win_chat, mode == 0 ? "Server: waiting..." : "Client: connecting...",
10, 10, 760, 20, N_GUI_ALIGN_LEFT);
/* chat log listbox */
listbox_log = n_gui_add_listbox(gui, win_chat, 10, 40, 760, 420, N_GUI_SELECT_NONE, NULL, NULL);
/* input textarea */
textarea_input = n_gui_add_textarea(gui, win_chat, 10, 470, 660, 30, 0, 256, NULL, NULL);
/* send button bound to Enter key (works even while typing in the single-line text input) */
btn_send = n_gui_add_button(gui, win_chat, "Send", 680, 470, 90, 30, N_GUI_SHAPE_ROUNDED, on_send_click, NULL);
n_gui_button_set_keycode(gui, btn_send, ALLEGRO_KEY_ENTER, 0);
/* server: connected clients window */
if (mode == 0) {
win_clients = n_gui_add_window(gui, "Connected Clients", 800, 10, 290, 580);
lbl_clients_header = n_gui_add_label(gui, win_clients, "Connected clients: 0",
10, 10, 270, 20, N_GUI_ALIGN_LEFT);
}
/* network setup */
int net_ok = FALSE;
if (mode == 0) {
/* server */
if (!pool) {
n_gui_label_set_text(gui, lbl_status, "Server: memory allocation failed");
chat_log_add("Failed to allocate server connection pool");
} else if (netw_make_listening(&netw_server, NULL, port_str, 10, NETWORK_IPALL) == TRUE) {
net_ok = TRUE;
char buf[128] = "";
snprintf(buf, sizeof(buf), "Server listening on port %s", port_str);
} else {
n_gui_label_set_text(gui, lbl_status, "Server: bind failed");
chat_log_add("Failed to bind server socket");
}
} else {
/* client */
net_ok = TRUE;
char buf[128] = "";
snprintf(buf, sizeof(buf), "Connected to %s:%s", server_addr, port_str);
} else {
n_gui_label_set_text(gui, lbl_status, "Client: connection failed");
chat_log_add("Failed to connect to server");
}
}
al_start_timer(timer);
int redraw = 1;
while (!DONE) {
ALLEGRO_EVENT ev;
al_wait_for_event(event_queue, &ev);
if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
DONE = 1;
continue;
}
if (ev.type == ALLEGRO_EVENT_KEY_DOWN && ev.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
DONE = 1;
continue;
}
if (ev.type == ALLEGRO_EVENT_TIMER) {
redraw = 1;
/* server: accept new connections */
if (mode == 0 && net_ok && netw_server) {
int retval = 0;
NETWORK* new_client = netw_accept_from_ex(netw_server, 0, 0, -1, &retval);
if (new_client) {
netw_start_thr_engine(new_client);
if (netw_pool_add(pool, new_client) != TRUE) {
/* Failed to track this client in the pool: close it to avoid leaks */
chat_log_add("Failed to add new client to pool, closing connection");
netw_close(&new_client);
} else {
char buf[128] = "";
snprintf(buf, sizeof(buf), "Client connected from %s (total: %zu)",
new_client->link.ip ? new_client->link.ip : "?",
}
}
}
/* poll messages */
if (mode == 0 && pool) {
/* server: poll each client in pool */
if (!dead_clients) {
n_log(LOG_ERR, "Could not allocate dead_clients list");
} else {
HT_FOREACH(node, pool->pool, {
NETWORK* cn = (NETWORK*)node->data.ptr;
if (cn) {
uint32_t state = 0;
int thr_state = 0;
netw_get_state(cn, &state, &thr_state);
if (state & NETW_EXITED || state & NETW_ERROR || state & NETW_EXIT_ASKED) {
list_push(dead_clients, cn, NULL);
} else {
poll_network_msgs(cn, cn->link.ip ? cn->link.ip : "client");
}
}
});
/* remove and close dead connections outside of the iteration */
int had_dead = 0;
list_foreach(dead_node, dead_clients) {
NETWORK* cn = (NETWORK*)dead_node->ptr;
char disc_buf[128] = "";
snprintf(disc_buf, sizeof(disc_buf), "Client %s:%s disconnected",
cn->link.ip ? cn->link.ip : "?",
cn->link.port ? cn->link.port : "?");
chat_log_add(disc_buf);
netw_close(&cn);
had_dead = 1;
}
if (had_dead) {
char sbuf[64] = "";
snprintf(sbuf, sizeof(sbuf), "Connected clients: %zu", netw_pool_nbclients(pool));
}
list_destroy(&dead_clients);
}
} else if (mode == 1 && netw_client) {
/* check connection status */
uint32_t state = 0;
int thr_state = 0;
netw_get_state(netw_client, &state, &thr_state);
if (state & NETW_EXITED || state & NETW_ERROR) {
n_gui_label_set_text(gui, lbl_status, "Disconnected from server");
chat_log_add("Disconnected from server");
}
}
}
if (redraw && al_is_event_queue_empty(event_queue)) {
redraw = 0;
al_clear_to_color(al_map_rgb(40, 40, 50));
al_flip_display();
}
}
/* cleanup */
if (netw_client) {
}
if (pool) {
/* netw_destroy_pool() does not close NETWORK objects; its internal
* destroy callback only logs. Collect all pool clients first, then
* close each one (netw_close also removes the client from the pool
* via netw_pool_remove), and finally destroy the now-empty pool. */
if (clients_to_close) {
HT_FOREACH(node, pool->pool, {
NETWORK* cn = (NETWORK*)node->data.ptr;
if (cn) {
list_push(clients_to_close, cn, NULL);
}
});
list_foreach(cnode, clients_to_close) {
NETWORK* cn = (NETWORK*)cnode->ptr;
netw_close(&cn);
}
list_destroy(&clients_to_close);
} else {
n_log(LOG_ERR, "failed to allocate client list for pool cleanup; pool clients may leak");
}
}
if (netw_server) {
}
netw_unload();
al_destroy_font(font);
al_destroy_timer(timer);
al_destroy_event_queue(event_queue);
al_destroy_display(display);
return 0;
}
static void usage(void)
int main(void)
int getoptret
Definition ex_fluid.c:60
int DONE
Definition ex_fluid.c:59
int log_level
Definition ex_fluid.c:61
ALLEGRO_DISPLAY * display
Definition ex_fluid.c:53
#define WIDTH
Definition ex_gui.c:36
static int lbl_status
Definition ex_gui.c:64
#define HEIGHT
Definition ex_gui.c:37
N_STR * build_chat_msg(const char *text)
build a chat network message
static NETWORK * netw_server
static int mode
static int textarea_input
static N_GUI_CTX * gui
static int win_clients
void poll_network_msgs(NETWORK *netw, const char *prefix)
process incoming messages on a single network connection
void refresh_clients_list(void)
refresh the connected clients listbox (server only)
#define NETMSG_CHAT
static int win_chat
static int listbox_log
static char * server_addr
static int lbl_clients_header
void on_send_click(int widget_id, void *user_data)
send button callback
static NETWORK * netw_client
static char * port_str
void chat_log_add(const char *text)
add a line to the chat log listbox
static int listbox_clients
static int btn_send
static NETWORK_POOL * pool
int decode_chat_msg(N_STR *str, char **out_text)
decode a received chat message
NETWORK * netw
Network for server mode, accepting incomming.
Definition ex_network.c:38
char * port
#define FreeNoLog(__ptr)
Free Handler without log.
Definition n_common.h:271
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:262
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
Set the display (viewport) size for global scrollbar computation.
Definition n_gui.c:5176
#define N_GUI_ALIGN_LEFT
left aligned text
Definition n_gui.h:156
void n_gui_textarea_set_text(N_GUI_CTX *ctx, int widget_id, const char *text)
set the text content of a textarea widget
Definition n_gui.c:1752
void n_gui_label_set_text(N_GUI_CTX *ctx, int widget_id, const char *text)
set the text of a label widget
Definition n_gui.c:2178
int n_gui_add_listbox(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, int selection_mode, void(*on_select)(int, int, int, void *), void *user_data)
Add a listbox widget.
Definition n_gui.c:1336
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
Definition n_gui.c:5767
int n_gui_add_label(N_GUI_CTX *ctx, int window_id, const char *text, float x, float y, float w, float h, int align)
Add a static text label.
Definition n_gui.c:1497
#define N_GUI_WIN_FIXED_POSITION
disable window dragging (default:enable)
Definition n_gui.h:196
#define N_GUI_SELECT_NONE
no selection allowed (display only)
Definition n_gui.h:140
int n_gui_listbox_add_item(N_GUI_CTX *ctx, int widget_id, const char *text)
add an item to a listbox widget
Definition n_gui.c:1860
void n_gui_draw(N_GUI_CTX *ctx)
Draw all visible windows and their widgets.
Definition n_gui.c:4800
void n_gui_button_set_keycode(N_GUI_CTX *ctx, int widget_id, int keycode, int modifiers)
Bind a keyboard key with optional modifier requirements to a button.
Definition n_gui.c:1129
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
Definition n_gui.c:581
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:466
void n_gui_listbox_clear(N_GUI_CTX *ctx, int widget_id)
remove all items from a listbox widget
Definition n_gui.c:1907
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:523
const char * n_gui_textarea_get_text(N_GUI_CTX *ctx, int widget_id)
get the text content of a textarea widget
Definition n_gui.c:1738
#define N_GUI_SHAPE_ROUNDED
rounded rectangle shape
Definition n_gui.h:116
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
Definition n_gui.c:843
int n_gui_add_textarea(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, int multiline, size_t char_limit, void(*on_change)(int, const char *, void *), void *user_data)
Add a text area widget.
Definition n_gui.c:1214
int n_gui_add_button(N_GUI_CTX *ctx, int window_id, const char *label, float x, float y, float w, float h, int shape, void(*on_click)(int, void *), void *user_data)
Add a button widget to a window.
Definition n_gui.c:1001
#define N_GUI_WIN_RESIZABLE
enable user-resizable window with a drag handle at bottom-right
Definition n_gui.h:194
The top-level GUI context that holds all windows.
Definition n_gui.h:882
#define HT_FOREACH(__ITEM_, __HASH_,...)
ForEach macro helper.
Definition n_hash.h:215
#define UNLIMITED_LIST_ITEMS
flag to pass to new_generic_list for an unlimited number of item in the list.
Definition n_list.h:72
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
Definition n_list.h:88
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:547
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
Definition n_list.c:36
#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:74
Structure of a generic LIST container.
Definition n_list.h:58
#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
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:201
N_STR * nstrdup(N_STR *str)
Duplicate a N_STR.
Definition n_str.c:708
A box including a string and his lenght.
Definition n_str.h:60
int get_str_from_msg(NETW_MSG *msg, char **value)
Get a string from a message string list.
int add_strdup_to_msg(NETW_MSG *msg, const char *str)
Add a copy of char *str to the string list in the message.
NETW_MSG * make_msg_from_str(N_STR *str)
Make a single message of the string.
N_STR * make_str_from_msg(NETW_MSG *msg)
Make a single string of the message.
int create_msg(NETW_MSG **msg)
Create a NETW_MSG *object.
int add_int_to_msg(NETW_MSG *msg, int value)
Add an int to the int list int the message.
int delete_msg(NETW_MSG **msg)
Delete a NETW_MSG *object.
int get_int_from_msg(NETW_MSG *msg, int *value)
Get an int from a message int list.
network message, array of char and int
char * ip
ip of the connected socket
Definition n_network.h:244
N_SOCKET link
networking socket
Definition n_network.h:326
HASH_TABLE * pool
table of clients
Definition n_network.h:373
N_STR * netw_get_msg(NETWORK *netw)
Get a message from aimed NETWORK.
Definition n_network.c:2977
int netw_add_msg(NETWORK *netw, N_STR *msg)
Add a message to send in aimed NETWORK.
Definition n_network.c:2914
NETWORK_POOL * netw_new_pool(size_t nb_min_element)
return a new network pool of nb_min_element
Definition n_network.c:3861
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:2240
int netw_start_thr_engine(NETWORK *netw)
Start the NETWORK netw Threaded Engine.
Definition n_network.c:3050
int netw_destroy_pool(NETWORK_POOL **netw_pool)
free a NETWORK_POOL *pool
Definition n_network.c:3880
size_t netw_pool_nbclients(NETWORK_POOL *netw_pool)
return the number of networks in netw_pool
Definition n_network.c:4019
NETWORK * netw_accept_from_ex(NETWORK *from, size_t send_list_limit, size_t recv_list_limit, int blocking, int *retval)
make a normal 'accept' .
Definition n_network.c:2713
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:47
int netw_pool_broadcast(NETWORK_POOL *netw_pool, const NETWORK *from, N_STR *net_msg)
add net_msg to all network in netork pool
Definition n_network.c:3995
int netw_get_state(NETWORK *netw, uint32_t *state, int *thr_engine_status)
Get the state of a network.
Definition n_network.c:1924
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:2041
int netw_set_blocking(NETWORK *netw, unsigned long int is_blocking)
Modify blocking socket mode.
Definition n_network.c:871
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:1814
int netw_pool_add(NETWORK_POOL *netw_pool, NETWORK *netw)
add a NETWORK *netw to a NETWORK_POOL *pool
Definition n_network.c:3912
int netw_pool_remove(NETWORK_POOL *netw_pool, NETWORK *netw)
remove a NETWORK *netw to a NETWORK_POOL *pool
Definition n_network.c:3958
@ NETW_EXITED
Definition n_network.h:232
@ NETW_ERROR
Definition n_network.h:232
Structure of a NETWORK.
Definition n_network.h:258
structure of a network pool
Definition n_network.h:371
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.
Network Engine.
Network messages , serialization tools.
N_STR and string function declaration.
Timing utilities.