ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
server.h File Reference

Go to the source code of this file.

Data Structures

struct  tcp_client_context_t
 Per-client connection context. More...
 
struct  tcp_server_config_t
 TCP server configuration. More...
 
struct  tcp_client_entry
 Client registry entry. More...
 
struct  tcp_server
 TCP server state. More...
 

Typedefs

typedef struct tcp_client_entry tcp_client_entry_t
 
typedef struct tcp_server tcp_server_t
 
typedef void(* tcp_client_cleanup_fn) (void *client_data)
 Callback for client cleanup.
 
typedef void(* tcp_client_foreach_fn) (socket_t socket, void *client_data, void *user_arg)
 Callback for iterating over clients.
 
typedef void(* tcp_status_update_fn) (void *user_data)
 Callback for periodic status updates.
 
typedef void *(* tcp_client_handler_fn) (void *arg)
 Client handler thread function type.
 

Functions

asciichat_error_t tcp_server_init (tcp_server_t *server, const tcp_server_config_t *config)
 Initialize TCP server.
 
asciichat_error_t tcp_server_run (tcp_server_t *server)
 Run TCP server accept loop.
 
void tcp_server_destroy (tcp_server_t *server)
 Shutdown TCP server.
 
void tcp_server_set_cleanup_callback (tcp_server_t *server, tcp_client_cleanup_fn cleanup_fn)
 Set client cleanup callback.
 
asciichat_error_t tcp_server_add_client (tcp_server_t *server, socket_t socket, void *client_data)
 Add client to registry.
 
asciichat_error_t tcp_server_remove_client (tcp_server_t *server, socket_t socket)
 Remove client from registry.
 
asciichat_error_t tcp_server_get_client (tcp_server_t *server, socket_t socket, void **out_data)
 Get client data.
 
void tcp_server_foreach_client (tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg)
 Iterate over all clients.
 
size_t tcp_server_get_client_count (tcp_server_t *server)
 Get client count.
 
const char * tcp_client_context_get_ip (const tcp_client_context_t *ctx, char *buf, size_t len)
 Get formatted IP address from client context.
 
int tcp_client_context_get_port (const tcp_client_context_t *ctx)
 Get port number from client context.
 
void tcp_server_reject_client (socket_t socket, const char *reason)
 Reject client connection with reason.
 
asciichat_error_t tcp_server_spawn_thread (tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
 Spawn a worker thread for a client.
 
asciichat_error_t tcp_server_stop_client_threads (tcp_server_t *server, socket_t client_socket)
 Stop all threads for a client in stop_id order.
 
asciichat_error_t tcp_server_get_thread_count (tcp_server_t *server, socket_t client_socket, size_t *count)
 Get thread count for a client.
 

Typedef Documentation

◆ tcp_client_cleanup_fn

typedef void(* tcp_client_cleanup_fn) (void *client_data)

Callback for client cleanup.

Called when a client is removed from the registry. Use this to free any allocated client_data.

Parameters
client_dataUser-provided client data to clean up

Definition at line 86 of file include/ascii-chat/network/tcp/server.h.

◆ tcp_client_entry_t

◆ tcp_client_foreach_fn

typedef void(* tcp_client_foreach_fn) (socket_t socket, void *client_data, void *user_arg)

Callback for iterating over clients.

Parameters
socketClient socket
client_dataUser-provided client data
user_argUser argument passed to foreach function

Definition at line 95 of file include/ascii-chat/network/tcp/server.h.

◆ tcp_client_handler_fn

typedef void *(* tcp_client_handler_fn) (void *arg)

Client handler thread function type.

Parameters
argPointer to tcp_client_context_t
Returns
NULL (thread exit value)

The handler must:

  • Close client_socket when done
  • Free the context structure

Definition at line 131 of file include/ascii-chat/network/tcp/server.h.

◆ tcp_server_t

typedef struct tcp_server tcp_server_t

Definition at line 76 of file include/ascii-chat/network/tcp/server.h.

◆ tcp_status_update_fn

typedef void(* tcp_status_update_fn) (void *user_data)

Callback for periodic status updates.

Called periodically from the accept loop timeout path. Use this to update status displays, refresh metrics, or perform housekeeping tasks.

Parameters
user_dataUser-provided data from config

Definition at line 105 of file include/ascii-chat/network/tcp/server.h.

Function Documentation

◆ tcp_client_context_get_ip()

const char * tcp_client_context_get_ip ( const tcp_client_context_t *  ctx,
char *  buf,
size_t  len 
)

Get formatted IP address from client context.

Extracts and formats the client IP address from the connection context. Works for both IPv4 and IPv6 addresses.

Parameters
ctxClient context structure
bufOutput buffer for formatted IP string
lenBuffer size (recommend INET6_ADDRSTRLEN = 46 bytes)
Returns
Pointer to buf on success, NULL on error

Definition at line 492 of file lib/network/tcp/server.c.

492 {
493 if (!ctx) {
494 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
495 return NULL;
496 }
497 if (!buf) {
498 SET_ERRNO(ERROR_INVALID_PARAM, "buf is NULL");
499 return NULL;
500 }
501 if (len == 0) {
502 SET_ERRNO(ERROR_INVALID_PARAM, "len is 0");
503 return NULL;
504 }
505
506 // Determine address family
507 int addr_family = (ctx->addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
508
509 // Format IP address using existing utility
510 if (format_ip_address(addr_family, (struct sockaddr *)&ctx->addr, buf, len) != 0) {
511 return NULL;
512 }
513
514 return buf;
515}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
@ ERROR_INVALID_PARAM
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Format IP address from socket address structure.
Definition ip.c:196
struct sockaddr_storage addr
Client address.

References tcp_client_context_t::addr, ERROR_INVALID_PARAM, format_ip_address(), and SET_ERRNO.

Referenced by acds_client_handler().

◆ tcp_client_context_get_port()

int tcp_client_context_get_port ( const tcp_client_context_t *  ctx)

Get port number from client context.

Extracts the client port number from the connection context. Works for both IPv4 and IPv6 addresses.

Parameters
ctxClient context structure
Returns
Port number (host byte order), or -1 on error

Definition at line 517 of file lib/network/tcp/server.c.

517 {
518 if (!ctx) {
519 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
520 return -1;
521 }
522
523 // Extract port based on address family
524 if (ctx->addr.ss_family == AF_INET) {
525 struct sockaddr_in *addr_in = (struct sockaddr_in *)&ctx->addr;
526 return ntohs(addr_in->sin_port);
527 } else if (ctx->addr.ss_family == AF_INET6) {
528 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&ctx->addr;
529 return ntohs(addr_in6->sin6_port);
530 }
531
532 SET_ERRNO(ERROR_INVALID_STATE, "Unknown address family: %d", ctx->addr.ss_family);
533 return -1;
534}
@ ERROR_INVALID_STATE

References tcp_client_context_t::addr, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, and SET_ERRNO.

◆ tcp_server_add_client()

asciichat_error_t tcp_server_add_client ( tcp_server_t *  server,
socket_t  socket,
void *  client_data 
)

Add client to registry.

Thread-safe registration of a connected client with arbitrary user data. The client_data pointer is stored as-is (caller retains ownership).

Parameters
serverServer structure
socketClient socket (used as lookup key)
client_dataUser-provided client data (can be NULL)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 366 of file lib/network/tcp/server.c.

366 {
367 if (!server) {
368 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
369 }
370
371 if (socket == INVALID_SOCKET_VALUE) {
372 return SET_ERRNO(ERROR_INVALID_PARAM, "socket is invalid");
373 }
374
375 // Allocate new entry
377 if (!entry) {
378 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate client entry");
379 }
380
381 entry->socket = socket;
382 entry->client_data = client_data;
383
384 // Create thread pool for this client
385 char pool_name[64];
386 SAFE_SNPRINTF(pool_name, sizeof(pool_name), "client_%d", socket);
387 entry->threads = thread_pool_create(pool_name);
388 if (!entry->threads) {
389 SAFE_FREE(entry);
390 return SET_ERRNO(ERROR_MEMORY, "Failed to create thread pool for client");
391 }
392
393 // Add to hash table (thread-safe with write lock)
395 HASH_ADD(hh, server->clients, socket, sizeof(socket_t), entry);
397
398 log_debug("Added client socket=%d to registry", socket);
399 return ASCIICHAT_OK;
400}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:492
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
#define rwlock_wrunlock(lock)
Release a write lock (with debug tracking in debug builds)
Definition rwlock.h:349
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:278
#define rwlock_wrlock(lock)
Acquire a write lock (with debug tracking in debug builds)
Definition rwlock.h:313
int socket_t
Client registry entry.
void * client_data
User-provided client data.
thread_pool_t * threads
Thread pool for client worker threads.
socket_t socket
Client socket (hash key)
tcp_client_entry_t * clients
Hash table of connected clients.
rwlock_t clients_rwlock
Read-write lock (allows concurrent readers, exclusive writers)
thread_pool_t * thread_pool_create(int num_threads)

References ASCIICHAT_OK, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, ERROR_MEMORY, INVALID_SOCKET_VALUE, log_debug, rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, SAFE_MALLOC, SAFE_SNPRINTF, SET_ERRNO, tcp_client_entry::socket, thread_pool_create(), and tcp_client_entry::threads.

Referenced by acds_client_handler(), acds_websocket_client_handler(), and add_client().

◆ tcp_server_destroy()

void tcp_server_destroy ( tcp_server_t *  server)

Shutdown TCP server.

Closes listen sockets and cleans up resources. Does NOT wait for client threads to exit (caller's responsibility).

Parameters
serverServer structure to clean up

Definition at line 302 of file lib/network/tcp/server.c.

302 {
303 if (!server) {
304 return;
305 }
306
307 log_debug("Shutting down TCP server...");
308
309 // Signal server to stop
310 atomic_store_bool(&server->running, false);
311
312 // Close listen sockets
313 if (server->listen_socket != INVALID_SOCKET_VALUE) {
314 log_debug("Closing IPv4 listen socket");
317 }
318
319 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
320 log_debug("Closing IPv6 listen socket");
323 }
324
325 // Clean up client registry
327
328 tcp_client_entry_t *entry = NULL, *tmp = NULL;
329 HASH_ITER(hh, server->clients, entry, tmp) {
330 // Call cleanup callback if set
331 if (server->cleanup_fn && entry->client_data) {
332 server->cleanup_fn(entry->client_data);
333 }
334
335 // Destroy thread pool (stops all threads and frees resources)
336 if (entry->threads) {
338 entry->threads = NULL;
339 }
340
341 HASH_DEL(server->clients, entry);
342 SAFE_FREE(entry);
343 }
344 server->clients = NULL;
345
348
349 // Note: This function does NOT wait for client threads to exit
350 // Caller is responsible for thread lifecycle management
351
352 log_debug("TCP server shutdown complete");
353}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
int rwlock_destroy(rwlock_t *lock)
Destroy a read-write lock.
int socket_close(socket_t sock)
Close a socket.
atomic_t running
Server running flag (set false to shutdown)
socket_t listen_socket
IPv4 listen socket.
socket_t listen_socket6
IPv6 listen socket.
tcp_client_cleanup_fn cleanup_fn
Callback for cleaning up client data.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.

References atomic_store_bool(), tcp_server::cleanup_fn, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_rwlock, INVALID_SOCKET_VALUE, tcp_server::listen_socket, tcp_server::listen_socket6, log_debug, tcp_server::running, rwlock_destroy(), rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, socket_close(), thread_pool_destroy(), and tcp_client_entry::threads.

Referenced by session_server_like_run().

◆ tcp_server_foreach_client()

void tcp_server_foreach_client ( tcp_server_t *  server,
tcp_client_foreach_fn  callback,
void *  user_arg 
)

Iterate over all clients.

Thread-safe iteration over all connected clients. The callback is called once per client while holding the clients mutex.

Parameters
serverServer structure
callbackFunction to call for each client
user_argUser argument passed to callback

Definition at line 461 of file lib/network/tcp/server.c.

461 {
462 if (!server || !callback) {
463 return;
464 }
465
467
468 tcp_client_entry_t *entry, *tmp;
469 HASH_ITER(hh, server->clients, entry, tmp) {
470 callback(entry->socket, entry->client_data, user_arg);
471 }
472
474}
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:294
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:331

References tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_rwlock, rwlock_rdlock, rwlock_rdunlock, and tcp_client_entry::socket.

Referenced by signaling_broadcast(), signaling_relay_ice(), and signaling_relay_sdp().

◆ tcp_server_get_client()

asciichat_error_t tcp_server_get_client ( tcp_server_t *  server,
socket_t  socket,
void **  out_data 
)

Get client data.

Thread-safe lookup of client data by socket.

Parameters
serverServer structure
socketClient socket to look up
out_dataOutput pointer for client data (set to NULL if not found)
Returns
ASCIICHAT_OK if found, ERROR_NOT_FOUND if not in registry

Definition at line 439 of file lib/network/tcp/server.c.

439 {
440 if (!server || !out_data) {
441 return SET_ERRNO(ERROR_INVALID_PARAM, "server or out_data is NULL");
442 }
443
445
446 tcp_client_entry_t *entry = NULL;
447 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
448
449 if (!entry) {
450 *out_data = NULL;
452 return SET_ERRNO(ERROR_INVALID_STATE, "Client socket=%d not in registry", socket);
453 }
454
455 *out_data = entry->client_data;
457
458 return ASCIICHAT_OK;
459}

References ASCIICHAT_OK, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, rwlock_rdlock, rwlock_rdunlock, and SET_ERRNO.

◆ tcp_server_get_client_count()

size_t tcp_server_get_client_count ( tcp_server_t *  server)

Get client count.

Thread-safe count of connected clients.

Parameters
serverServer structure
Returns
Number of clients in registry

Definition at line 476 of file lib/network/tcp/server.c.

476 {
477 if (!server) {
478 return 0;
479 }
480
482 size_t count = HASH_COUNT(server->clients);
484
485 return count;
486}

References tcp_server::clients, tcp_server::clients_rwlock, rwlock_rdlock, and rwlock_rdunlock.

Referenced by acds_client_handler(), acds_server_shutdown(), acds_websocket_client_handler(), and ui_status_gather().

◆ tcp_server_get_thread_count()

asciichat_error_t tcp_server_get_thread_count ( tcp_server_t *  server,
socket_t  client_socket,
size_t *  count 
)

Get thread count for a client.

Thread-safe count of worker threads spawned for a client.

Parameters
serverServer structure
client_socketClient socket to query
[out]countOutput pointer for thread count (set to 0 if client not found)
Returns
ASCIICHAT_OK on success, ERROR_NOT_FOUND if client not in registry

Definition at line 637 of file lib/network/tcp/server.c.

637 {
638 if (!server || !count) {
639 return SET_ERRNO(ERROR_INVALID_PARAM, "server or count is NULL");
640 }
641
642 if (client_socket == INVALID_SOCKET_VALUE) {
643 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
644 }
645
646 *count = 0;
647
648 // Find client entry
650 tcp_client_entry_t *entry = NULL;
651 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
652
653 if (!entry) {
655 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
656 }
657
658 // Get thread count from pool
659 if (entry->threads) {
660 *count = thread_pool_get_count(entry->threads);
661 }
662
664
665 return ASCIICHAT_OK;
666}
@ ERROR_NOT_FOUND
size_t thread_pool_get_count(const thread_pool_t *pool)
Get thread count in the pool.

References ASCIICHAT_OK, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, rwlock_rdlock, rwlock_rdunlock, SET_ERRNO, thread_pool_get_count(), and tcp_client_entry::threads.

◆ tcp_server_init()

asciichat_error_t tcp_server_init ( tcp_server_t *  server,
const tcp_server_config_t *  config 
)

Initialize TCP server.

Creates and binds TCP sockets according to configuration. At least one of IPv4 or IPv6 must be successfully bound.

Parameters
serverServer structure to initialize
configServer configuration
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 105 of file lib/network/tcp/server.c.

105 {
106 if (!server || !config) {
107 return SET_ERRNO(ERROR_INVALID_PARAM, "server or config is NULL");
108 }
109
110 // Note: client_handler is optional - some users may use tcp_server just for socket setup
111 // and implement their own accept loop (like ascii-chat server with its cleanup logic)
112
113 // Initialize server state
114 memset(server, 0, sizeof(*server));
117 atomic_store_bool(&server->running, true);
118 server->config = *config; // Copy config
119
120 // Initialize client registry
121 server->clients = NULL; // uthash starts with NULL
122 server->cleanup_fn = NULL;
123 if (rwlock_init(&server->clients_rwlock, "clients") != 0) {
124 return SET_ERRNO(ERROR_THREAD, "Failed to initialize clients read-write lock");
125 }
126
127 // Determine which IP versions to bind
128 bool should_bind_ipv4 = config->bind_ipv4;
129 bool should_bind_ipv6 = config->bind_ipv6;
130
131 // Bind IPv4 socket if requested
132 if (should_bind_ipv4) {
133 const char *ipv4_addr = (config->ipv4_address && config->ipv4_address[0] != '\0') ? config->ipv4_address : NULL;
134 server->listen_socket = bind_and_listen(ipv4_addr, AF_INET, config->port);
135
136 if (server->listen_socket == INVALID_SOCKET_VALUE) {
137 log_warn("Failed to bind IPv4 socket");
138 }
139 }
140
141 // Bind IPv6 socket if requested
142 if (should_bind_ipv6) {
143 const char *ipv6_addr = (config->ipv6_address && config->ipv6_address[0] != '\0') ? config->ipv6_address : NULL;
144 server->listen_socket6 = bind_and_listen(ipv6_addr, AF_INET6, config->port);
145
146 if (server->listen_socket6 == INVALID_SOCKET_VALUE) {
147 log_warn("Failed to bind IPv6 socket");
148 }
149 }
150
151 // Ensure at least one socket bound successfully
153 return SET_ERRNO(ERROR_NETWORK_BIND, "Failed to bind any sockets (IPv4 and IPv6 both failed)");
154 }
155
156 /* Register server with named registry, identified by port */
157 char port_name[32];
158 snprintf(port_name, sizeof(port_name), "server:%d", server->config.port);
159 NAMED_REGISTER(server, port_name, "tcp_server", "0x%tx", NULL);
160
161 /* Register server's sync primitives with hierarchical naming */
162 NAMED_REGISTER_RWLOCK(&server->clients_rwlock, "clients_rwlock", (uintptr_t)(const void *)(server));
163 NAMED_REGISTER_ATOMIC(&server->running, "is_running", (uintptr_t)(const void *)(server));
164
165 return ASCIICHAT_OK;
166}
#define NAMED_REGISTER_RWLOCK(lock, name, parent_ptr)
Register an rwlock with automatic format specifier.
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix)
@ ERROR_NETWORK_BIND
Definition error_codes.h:78
@ ERROR_THREAD
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
int rwlock_init(rwlock_t *rwlock, const char *name)
Initialize a read-write lock with a name.
Definition threading.c:65
bool bind_ipv6
Whether to bind IPv6 socket.
bool bind_ipv4
Whether to bind IPv4 socket.
const char * ipv4_address
IPv4 bind address (NULL or empty = don't bind)
const char * ipv6_address
IPv6 bind address (NULL or empty = don't bind)
tcp_server_config_t config
Server configuration.

References ASCIICHAT_OK, atomic_store_bool(), tcp_server_config_t::bind_ipv4, tcp_server_config_t::bind_ipv6, tcp_server::cleanup_fn, tcp_server::clients, tcp_server::clients_rwlock, tcp_server::config, ERROR_INVALID_PARAM, ERROR_NETWORK_BIND, ERROR_THREAD, INVALID_SOCKET_VALUE, tcp_server_config_t::ipv4_address, tcp_server_config_t::ipv6_address, tcp_server::listen_socket, tcp_server::listen_socket6, log_warn, NAMED_REGISTER, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_RWLOCK, tcp_server_config_t::port, tcp_server::running, rwlock_init(), and SET_ERRNO.

Referenced by session_server_like_run().

◆ tcp_server_reject_client()

void tcp_server_reject_client ( socket_t  socket,
const char *  reason 
)

Reject client connection with reason.

Helper for rejecting clients due to rate limits, capacity limits, etc. Logs the rejection reason and closes the socket.

Parameters
socketClient socket to close
reasonHuman-readable rejection reason (for logging)

Definition at line 536 of file lib/network/tcp/server.c.

536 {
537 if (socket == INVALID_SOCKET_VALUE) {
538 SET_ERRNO(ERROR_INVALID_PARAM, "socket is INVALID_SOCKET_VALUE");
539 return;
540 }
541
542 log_warn("Rejecting client connection: %s", reason ? reason : "unknown reason");
543 socket_close(socket);
544}

References ERROR_INVALID_PARAM, INVALID_SOCKET_VALUE, log_warn, SET_ERRNO, and socket_close().

Referenced by acds_client_handler().

◆ tcp_server_remove_client()

asciichat_error_t tcp_server_remove_client ( tcp_server_t *  server,
socket_t  socket 
)

Remove client from registry.

Thread-safe removal of a client. If a cleanup callback is set, it will be called with the client_data before removal.

Parameters
serverServer structure
socketClient socket to remove
Returns
ASCIICHAT_OK if removed, ERROR_NOT_FOUND if not in registry

Definition at line 402 of file lib/network/tcp/server.c.

402 {
403 if (!server) {
404 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
405 }
406
408
409 tcp_client_entry_t *entry = NULL;
410 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
411
412 if (!entry) {
414 // Already removed (e.g., during shutdown) - this is fine
415 log_debug("Client socket=%d already removed from registry", socket);
416 return ASCIICHAT_OK;
417 }
418
419 // Call cleanup callback if set
420 if (server->cleanup_fn && entry->client_data) {
421 server->cleanup_fn(entry->client_data);
422 }
423
424 // Destroy thread pool (stops all threads and frees resources)
425 if (entry->threads) {
427 entry->threads = NULL;
428 }
429
430 HASH_DEL(server->clients, entry);
431 SAFE_FREE(entry);
432
434
435 log_debug("Removed client socket=%d from registry", socket);
436 return ASCIICHAT_OK;
437}

References ASCIICHAT_OK, tcp_server::cleanup_fn, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, log_debug, rwlock_wrlock, rwlock_wrunlock, SAFE_FREE, SET_ERRNO, thread_pool_destroy(), and tcp_client_entry::threads.

Referenced by acds_client_handler(), and acds_websocket_client_handler().

◆ tcp_server_run()

asciichat_error_t tcp_server_run ( tcp_server_t *  server)

Run TCP server accept loop.

Accepts client connections and spawns handler threads. Blocks until server->running is set to false.

Uses select() with timeout to handle dual-stack sockets and allow responsive shutdown. If a status_update_fn is configured, it will be called periodically on each select() timeout.

Parameters
serverInitialized server structure
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 168 of file lib/network/tcp/server.c.

168 {
169 if (!server) {
170 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
171 }
172
173 if (!server->config.client_handler) {
175 "client_handler is required for tcp_server_run() - use custom accept loop if handler is NULL");
176 }
177
178 log_debug("TCP server starting accept loop...");
179
180 while (atomic_load_bool(&server->running)) {
181 // Build fd_set for select()
182 fd_set read_fds;
183 socket_fd_zero(&read_fds);
184 socket_t max_fd = 0;
185
186 // Add IPv4 socket if available
187 if (server->listen_socket != INVALID_SOCKET_VALUE) {
188 socket_fd_set(server->listen_socket, &read_fds);
189 max_fd = server->listen_socket > max_fd ? server->listen_socket : max_fd;
190 }
191
192 // Add IPv6 socket if available
193 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
194 socket_fd_set(server->listen_socket6, &read_fds);
195 max_fd = server->listen_socket6 > max_fd ? server->listen_socket6 : max_fd;
196 }
197
198 // Use timeout from config (defaults to 1 second if not set)
199 // Convert double seconds to tv_sec and tv_usec
200 double timeout_sec_double = server->config.accept_timeout_sec > 0 ? server->config.accept_timeout_sec : 1.0;
201 time_t timeout_sec = (time_t)timeout_sec_double;
202 long timeout_usec = (long)((timeout_sec_double - timeout_sec) * (double)US_PER_SEC_INT);
203 struct timeval timeout = {.tv_sec = timeout_sec, .tv_usec = timeout_usec};
204
205 int select_result = socket_select((int)max_fd, &read_fds, NULL, NULL, &timeout);
206
207 if (select_result < 0) {
208 // Check if interrupted by signal (expected during shutdown)
209 int err = socket_get_last_error();
210 if (err == EINTR) {
211 // Signal interrupt (e.g., SIGTERM, SIGINT) - check running flag and continue
212 log_debug("select() interrupted by signal");
213 continue;
214 }
215 // Actual error - socket_get_error_string() returns last socket error
216 log_error("select() failed in accept loop: %s (errno=%d)", socket_get_error_string(), err);
217 continue;
218 }
219
220 if (select_result == 0) {
221 // Timeout - invoke status update callback if configured
222 if (server->config.status_update_fn) {
224 }
225 log_debug_every(5000, "select() timeout on TCP server (no incoming connections)");
226 continue;
227 }
228
229 // Check which socket has incoming connection
230 socket_t ready_socket = INVALID_SOCKET_VALUE;
231 if (server->listen_socket != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket, &read_fds)) {
232 ready_socket = server->listen_socket;
233 } else if (server->listen_socket6 != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket6, &read_fds)) {
234 ready_socket = server->listen_socket6;
235 }
236
237 if (ready_socket == INVALID_SOCKET_VALUE) {
238 // Spurious wakeup
239 continue;
240 }
241
242 // Accept connection
243 struct sockaddr_storage client_addr;
244 socklen_t client_addr_len = sizeof(client_addr);
245 socket_t client_socket = accept(ready_socket, (struct sockaddr *)&client_addr, &client_addr_len);
246 if (socket_is_valid(client_socket)) {
247 NAMED_REGISTER_SOCKET(client_socket, "tcp_client", NULL);
248 }
249
250 if (client_socket == INVALID_SOCKET_VALUE) {
251 log_warn("Failed to accept connection");
252 continue;
253 }
254
255 // Format client IP for logging
256 char client_ip[INET6_ADDRSTRLEN];
257 // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) - client_addr filled by accept()
258 int addr_family = (client_addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
259 if (format_ip_address(addr_family, (struct sockaddr *)&client_addr, client_ip, sizeof(client_ip)) != ASCIICHAT_OK) {
260 SAFE_STRNCPY(client_ip, "(unknown)", sizeof(client_ip));
261 }
262
263 log_debug("Accepted connection from %s", client_ip);
264
265 // Allocate client context
267 if (!ctx) {
268 log_error("Failed to allocate client context");
269 socket_close(client_socket);
270 continue;
271 }
272
273 ctx->client_socket = client_socket;
274 ctx->addr = client_addr;
275 ctx->addr_len = client_addr_len;
276 ctx->user_data = server->config.user_data;
277
278 // Spawn client handler thread
279 // Handler is responsible for:
280 // 1. Allocating client_data
281 // 2. Calling tcp_server_add_client() to register
282 // 3. Spawning additional worker threads via tcp_server_spawn_thread() if needed
283 // 4. Processing client requests
284 // 5. Calling tcp_server_remove_client() on disconnect
285 // 6. Closing socket and freeing ctx
286 asciichat_thread_t thread;
287 if (asciichat_thread_create(&thread, "tcp_client", server->config.client_handler, ctx) != 0) {
288 log_error("Failed to create client handler thread for %s", client_ip);
289 SAFE_FREE(ctx);
290 socket_close(client_socket);
291 continue;
292 }
293
294 // Thread is detached (handler is responsible for cleanup)
295 (void)thread; // Suppress unused warning
296 }
297
298 log_debug("TCP server accept loop exited");
299 return ASCIICHAT_OK;
300}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define NAMED_REGISTER_SOCKET(socket, name, parent_ptr)
Register a socket with automatic format specifier.
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define US_PER_SEC_INT
Definition time.h:161
void socket_fd_zero(fd_set *set)
Clear an fd_set.
#define EINTR
bool socket_is_valid(socket_t sock)
Check if a socket handle is valid.
void socket_fd_set(socket_t sock, fd_set *set)
Add a socket to an fd_set.
int socket_get_last_error(void)
Get last socket error code.
int socket_select(socket_t max_fd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
Select sockets for I/O readiness.
const char * socket_get_error_string(void)
Get last socket error as string.
int socket_fd_isset(socket_t sock, fd_set *set)
Check if a socket is in an fd_set.
#define asciichat_thread_create(thread_ptr, attr, start_routine, arg)
void * asciichat_thread_t
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:702
socket_t client_socket
Client connection socket.
void * user_data
User-provided data from config.
void * status_update_data
User data passed to status update callback.
void * user_data
User data passed to each client handler.
tcp_client_handler_fn client_handler
Client handler callback.
double accept_timeout_sec
select() timeout in seconds (supports decimals, e.g. 0.05 for 50ms)
tcp_status_update_fn status_update_fn
Optional status update callback (called on timeout, NULL to disable)

References tcp_server_config_t::accept_timeout_sec, tcp_client_context_t::addr, tcp_client_context_t::addr_len, ASCIICHAT_OK, asciichat_thread_create, atomic_load_bool(), tcp_server_config_t::client_handler, tcp_client_context_t::client_socket, tcp_server::config, EINTR, ERROR_INVALID_PARAM, format_ip_address(), INVALID_SOCKET_VALUE, tcp_server::listen_socket, tcp_server::listen_socket6, log_debug, log_debug_every, log_error, log_warn, NAMED_REGISTER_SOCKET, tcp_server::running, SAFE_FREE, SAFE_MALLOC, SAFE_STRNCPY, SET_ERRNO, socket_close(), socket_fd_isset(), socket_fd_set(), socket_fd_zero(), socket_get_error_string(), socket_get_last_error(), socket_is_valid(), socket_select(), tcp_server_config_t::status_update_data, tcp_server_config_t::status_update_fn, US_PER_SEC_INT, tcp_client_context_t::user_data, and tcp_server_config_t::user_data.

Referenced by session_server_like_run().

◆ tcp_server_set_cleanup_callback()

void tcp_server_set_cleanup_callback ( tcp_server_t *  server,
tcp_client_cleanup_fn  cleanup_fn 
)

Set client cleanup callback.

Sets the callback function that will be called when a client is removed from the registry. Use this to free any allocated client_data.

Parameters
serverServer structure
cleanup_fnCleanup callback (or NULL to disable)

Definition at line 359 of file lib/network/tcp/server.c.

359 {
360 if (!server) {
361 return;
362 }
363 server->cleanup_fn = cleanup_fn;
364}

References tcp_server::cleanup_fn.

◆ tcp_server_spawn_thread()

asciichat_error_t tcp_server_spawn_thread ( tcp_server_t *  server,
socket_t  client_socket,
void *(*)(void *)  thread_func,
void *  thread_arg,
int  stop_id,
const char *  thread_name 
)

Spawn a worker thread for a client.

Creates and tracks a new worker thread for the specified client. Threads are identified by stop_id for ordered cleanup - lower stop_id values are stopped first when the client disconnects.

Example stop_id ordering:

  • stop_id=1: Receive thread (stop first to prevent new data)
  • stop_id=2: Render threads (stop after receive)
  • stop_id=3: Send thread (stop last after all processing done)
Parameters
serverServer structure
client_socketClient socket to spawn thread for
thread_funcThread function to execute
thread_argArgument passed to thread function
stop_idCleanup order (lower = stop first)
thread_nameThread name for debugging (max 63 chars)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 550 of file lib/network/tcp/server.c.

551 {
552 if (!server || !thread_func) {
553 return SET_ERRNO(ERROR_INVALID_PARAM, "server or thread_func is NULL");
554 }
555
556 // For WebRTC clients (no socket): create thread directly without TCP server thread pool tracking
557 // This allows render threads to work for both TCP and WebRTC clients
558 if (client_socket == INVALID_SOCKET_VALUE) {
559 // Extract thread handle from thread_arg if it's a client_info_t
560 // We need to store the thread handle somewhere, but for now just create the thread
561 // The caller must manage the thread handle directly for WebRTC clients
562 log_debug("Spawning standalone thread '%s' (no socket, WebRTC client)", thread_name ? thread_name : "unnamed");
563
564 // For WebRTC clients, we can't use thread_pool since there's no socket entry
565 // Instead, create the thread directly - the caller must handle the thread
566 // This is a hack to allow reusing tcp_server_spawn_thread for WebRTC
567 // In the future, consider a unified thread management system
568 asciichat_thread_t temp_thread;
569 asciichat_error_t result = asciichat_thread_create(&temp_thread, "tcp_worker", thread_func, thread_arg);
570 if (result != ASCIICHAT_OK) {
571 return result;
572 }
573
574 // Note: temp_thread handle is lost here, but the thread is running
575 // The caller must manage thread lifecycle for WebRTC clients differently
576 (void)temp_thread; // Suppress unused warning
577 return ASCIICHAT_OK;
578 }
579
580 // Find client entry
582 tcp_client_entry_t *entry = NULL;
583 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
584
585 if (!entry) {
587 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
588 }
589
590 // Spawn thread in client's thread pool
591 asciichat_error_t result = thread_pool_spawn(entry->threads, thread_func, thread_arg, stop_id, thread_name);
592
594
595 if (result != ASCIICHAT_OK) {
596 return result;
597 }
598
599 size_t thread_count = thread_pool_get_count(entry->threads);
600 log_debug("Spawned thread '%s' (stop_id=%d) for client socket=%d (total_threads=%zu)",
601 thread_name ? thread_name : "unnamed", stop_id, client_socket, thread_count);
602
603 return ASCIICHAT_OK;
604}
asciichat_error_t thread_pool_spawn(void *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *name)
Spawn a worker thread in the pool.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49

References ASCIICHAT_OK, asciichat_thread_create, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, log_debug, rwlock_rdlock, rwlock_rdunlock, SET_ERRNO, thread_pool_get_count(), thread_pool_spawn(), and tcp_client_entry::threads.

Referenced by create_client_render_threads().

◆ tcp_server_stop_client_threads()

asciichat_error_t tcp_server_stop_client_threads ( tcp_server_t *  server,
socket_t  client_socket 
)

Stop all threads for a client in stop_id order.

Stops all worker threads spawned for the specified client. Threads are stopped in ascending stop_id order (lower values first). Joins each thread to ensure it has fully exited before proceeding.

Parameters
serverServer structure
client_socketClient socket whose threads to stop
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 606 of file lib/network/tcp/server.c.

606 {
607 if (!server) {
608 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
609 }
610
611 if (client_socket == INVALID_SOCKET_VALUE) {
612 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
613 }
614
615 // Find client entry
617 tcp_client_entry_t *entry = NULL;
618 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
619
620 if (!entry) {
622 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
623 }
624
625 // Stop all threads in client's thread pool (in stop_id order)
627 if (entry->threads) {
628 result = thread_pool_stop_all(entry->threads);
629 }
630
632
633 log_debug("All threads stopped for client socket=%d", client_socket);
634 return result;
635}
asciichat_error_t thread_pool_stop_all(void *pool)
Stop all threads in the pool in stop_id order.

References ASCIICHAT_OK, tcp_server::clients, tcp_server::clients_rwlock, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, log_debug, rwlock_rdlock, rwlock_rdunlock, SET_ERRNO, thread_pool_stop_all(), and tcp_client_entry::threads.

Referenced by remove_client().