ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
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_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_shutdown (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 84 of file lib/network/tcp/server.h.

◆ tcp_client_entry_t

Definition at line 73 of file lib/network/tcp/server.h.

◆ 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 93 of file lib/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 119 of file lib/network/tcp/server.h.

◆ tcp_server_t

typedef struct tcp_server tcp_server_t

Definition at line 74 of file lib/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 463 of file lib/network/tcp/server.c.

463 {
464 if (!ctx) {
465 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
466 return NULL;
467 }
468 if (!buf) {
469 SET_ERRNO(ERROR_INVALID_PARAM, "buf is NULL");
470 return NULL;
471 }
472 if (len == 0) {
473 SET_ERRNO(ERROR_INVALID_PARAM, "len is 0");
474 return NULL;
475 }
476
477 // Determine address family
478 int addr_family = (ctx->addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
479
480 // Format IP address using existing utility
481 if (format_ip_address(addr_family, (struct sockaddr *)&ctx->addr, buf, len) != 0) {
482 return NULL;
483 }
484
485 return buf;
486}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ 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:205
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 488 of file lib/network/tcp/server.c.

488 {
489 if (!ctx) {
490 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
491 return -1;
492 }
493
494 // Extract port based on address family
495 if (ctx->addr.ss_family == AF_INET) {
496 struct sockaddr_in *addr_in = (struct sockaddr_in *)&ctx->addr;
497 return ntohs(addr_in->sin_port);
498 } else if (ctx->addr.ss_family == AF_INET6) {
499 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&ctx->addr;
500 return ntohs(addr_in6->sin6_port);
501 }
502
503 SET_ERRNO(ERROR_INVALID_STATE, "Unknown address family: %d", ctx->addr.ss_family);
504 return -1;
505}
@ 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 337 of file lib/network/tcp/server.c.

337 {
338 if (!server) {
339 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
340 }
341
342 if (socket == INVALID_SOCKET_VALUE) {
343 return SET_ERRNO(ERROR_INVALID_PARAM, "socket is invalid");
344 }
345
346 // Allocate new entry
348 if (!entry) {
349 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate client entry");
350 }
351
352 entry->socket = socket;
353 entry->client_data = client_data;
354
355 // Create thread pool for this client
356 char pool_name[64];
357 SAFE_SNPRINTF(pool_name, sizeof(pool_name), "client_%d", socket);
358 entry->threads = thread_pool_create(pool_name);
359 if (!entry->threads) {
360 SAFE_FREE(entry);
361 return SET_ERRNO(ERROR_MEMORY, "Failed to create thread pool for client");
362 }
363
364 // Add to hash table (thread-safe)
365 mutex_lock(&server->clients_mutex);
366 HASH_ADD(hh, server->clients, socket, sizeof(socket_t), entry);
367 mutex_unlock(&server->clients_mutex);
368
369 log_debug("Added client socket=%d to registry", socket);
370 return ASCIICHAT_OK;
371}
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_SNPRINTF(buffer, buffer_size,...)
Definition common.h:412
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#define log_debug(...)
Log a DEBUG message.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
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)
mutex_t clients_mutex
Mutex protecting client registry.
tcp_client_entry_t * clients
Hash table of connected clients.
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
Definition thread_pool.c:12

References ASCIICHAT_OK, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_mutex, ERROR_INVALID_PARAM, ERROR_MEMORY, INVALID_SOCKET_VALUE, log_debug, mutex_lock, mutex_unlock, 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().

◆ 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 432 of file lib/network/tcp/server.c.

432 {
433 if (!server || !callback) {
434 return;
435 }
436
437 mutex_lock(&server->clients_mutex);
438
439 tcp_client_entry_t *entry, *tmp;
440 HASH_ITER(hh, server->clients, entry, tmp) {
441 callback(entry->socket, entry->client_data, user_arg);
442 }
443
444 mutex_unlock(&server->clients_mutex);
445}

References tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_mutex, mutex_lock, mutex_unlock, 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 410 of file lib/network/tcp/server.c.

410 {
411 if (!server || !out_data) {
412 return SET_ERRNO(ERROR_INVALID_PARAM, "server or out_data is NULL");
413 }
414
415 mutex_lock(&server->clients_mutex);
416
417 tcp_client_entry_t *entry = NULL;
418 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
419
420 if (!entry) {
421 *out_data = NULL;
422 mutex_unlock(&server->clients_mutex);
423 return SET_ERRNO(ERROR_INVALID_STATE, "Client socket=%d not in registry", socket);
424 }
425
426 *out_data = entry->client_data;
427 mutex_unlock(&server->clients_mutex);
428
429 return ASCIICHAT_OK;
430}

References ASCIICHAT_OK, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_mutex, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, mutex_lock, mutex_unlock, 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 447 of file lib/network/tcp/server.c.

447 {
448 if (!server) {
449 return 0;
450 }
451
452 mutex_lock(&server->clients_mutex);
453 size_t count = HASH_COUNT(server->clients);
454 mutex_unlock(&server->clients_mutex);
455
456 return count;
457}

References tcp_server::clients, tcp_server::clients_mutex, mutex_lock, and mutex_unlock.

Referenced by acds_client_handler(), and acds_server_shutdown().

◆ 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 588 of file lib/network/tcp/server.c.

588 {
589 if (!server || !count) {
590 return SET_ERRNO(ERROR_INVALID_PARAM, "server or count is NULL");
591 }
592
593 if (client_socket == INVALID_SOCKET_VALUE) {
594 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
595 }
596
597 *count = 0;
598
599 // Find client entry
600 mutex_lock(&server->clients_mutex);
601 tcp_client_entry_t *entry = NULL;
602 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
603
604 if (!entry) {
605 mutex_unlock(&server->clients_mutex);
606 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
607 }
608
609 // Get thread count from pool
610 if (entry->threads) {
611 *count = thread_pool_get_count(entry->threads);
612 }
613
614 mutex_unlock(&server->clients_mutex);
615
616 return ASCIICHAT_OK;
617}
@ 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_mutex, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, mutex_lock, mutex_unlock, 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 96 of file lib/network/tcp/server.c.

96 {
97 if (!server || !config) {
98 return SET_ERRNO(ERROR_INVALID_PARAM, "server or config is NULL");
99 }
100
101 // Note: client_handler is optional - some users may use tcp_server just for socket setup
102 // and implement their own accept loop (like ascii-chat server with its cleanup logic)
103
104 // Initialize server state
105 memset(server, 0, sizeof(*server));
108 atomic_store(&server->running, true);
109 server->config = *config; // Copy config
110
111 // Initialize client registry
112 server->clients = NULL; // uthash starts with NULL
113 server->cleanup_fn = NULL;
114 if (mutex_init(&server->clients_mutex) != 0) {
115 return SET_ERRNO(ERROR_THREAD, "Failed to initialize clients mutex");
116 }
117
118 // Determine which IP versions to bind
119 bool should_bind_ipv4 = config->bind_ipv4;
120 bool should_bind_ipv6 = config->bind_ipv6;
121
122 // Bind IPv4 socket if requested
123 if (should_bind_ipv4) {
124 const char *ipv4_addr = (config->ipv4_address && config->ipv4_address[0] != '\0') ? config->ipv4_address : NULL;
125 server->listen_socket = bind_and_listen(ipv4_addr, AF_INET, config->port);
126
127 if (server->listen_socket == INVALID_SOCKET_VALUE) {
128 log_warn("Failed to bind IPv4 socket");
129 }
130 }
131
132 // Bind IPv6 socket if requested
133 if (should_bind_ipv6) {
134 const char *ipv6_addr = (config->ipv6_address && config->ipv6_address[0] != '\0') ? config->ipv6_address : NULL;
135 server->listen_socket6 = bind_and_listen(ipv6_addr, AF_INET6, config->port);
136
137 if (server->listen_socket6 == INVALID_SOCKET_VALUE) {
138 log_warn("Failed to bind IPv6 socket");
139 }
140 }
141
142 // Ensure at least one socket bound successfully
144 return SET_ERRNO(ERROR_NETWORK_BIND, "Failed to bind any sockets (IPv4 and IPv6 both failed)");
145 }
146
147 return ASCIICHAT_OK;
148}
@ ERROR_NETWORK_BIND
Definition error_codes.h:70
@ ERROR_THREAD
Definition error_codes.h:95
#define log_warn(...)
Log a WARN message.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
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)
atomic_bool running
Server running flag (set false to shutdown)
tcp_server_config_t config
Server configuration.
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.

References ASCIICHAT_OK, tcp_server_config_t::bind_ipv4, tcp_server_config_t::bind_ipv6, tcp_server::cleanup_fn, tcp_server::clients, tcp_server::clients_mutex, 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, mutex_init(), tcp_server_config_t::port, tcp_server::running, and SET_ERRNO.

Referenced by acds_server_init(), and server_main().

◆ 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 507 of file lib/network/tcp/server.c.

507 {
508 if (socket == INVALID_SOCKET_VALUE) {
509 SET_ERRNO(ERROR_INVALID_PARAM, "socket is INVALID_SOCKET_VALUE");
510 return;
511 }
512
513 log_warn("Rejecting client connection: %s", reason ? reason : "unknown reason");
514 socket_close(socket);
515}
int socket_close(socket_t sock)
Close a socket.

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 373 of file lib/network/tcp/server.c.

373 {
374 if (!server) {
375 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
376 }
377
378 mutex_lock(&server->clients_mutex);
379
380 tcp_client_entry_t *entry = NULL;
381 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
382
383 if (!entry) {
384 mutex_unlock(&server->clients_mutex);
385 // Already removed (e.g., during shutdown) - this is fine
386 log_debug("Client socket=%d already removed from registry", socket);
387 return ASCIICHAT_OK;
388 }
389
390 // Call cleanup callback if set
391 if (server->cleanup_fn && entry->client_data) {
392 server->cleanup_fn(entry->client_data);
393 }
394
395 // Destroy thread pool (stops all threads and frees resources)
396 if (entry->threads) {
398 entry->threads = NULL;
399 }
400
401 HASH_DEL(server->clients, entry);
402 SAFE_FREE(entry);
403
404 mutex_unlock(&server->clients_mutex);
405
406 log_debug("Removed client socket=%d from registry", socket);
407 return ASCIICHAT_OK;
408}
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.
Definition thread_pool.c:43

References ASCIICHAT_OK, tcp_server::cleanup_fn, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_mutex, ERROR_INVALID_PARAM, log_debug, mutex_lock, mutex_unlock, SAFE_FREE, SET_ERRNO, thread_pool_destroy(), and tcp_client_entry::threads.

Referenced by acds_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.

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

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

150 {
151 if (!server) {
152 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
153 }
154
155 if (!server->config.client_handler) {
157 "client_handler is required for tcp_server_run() - use custom accept loop if handler is NULL");
158 }
159
160 log_info("TCP server starting accept loop...");
161
162 while (atomic_load(&server->running)) {
163 // Build fd_set for select()
164 fd_set read_fds;
165 socket_fd_zero(&read_fds);
166 socket_t max_fd = 0;
167
168 // Add IPv4 socket if available
169 if (server->listen_socket != INVALID_SOCKET_VALUE) {
170 socket_fd_set(server->listen_socket, &read_fds);
171 max_fd = server->listen_socket > max_fd ? server->listen_socket : max_fd;
172 }
173
174 // Add IPv6 socket if available
175 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
176 socket_fd_set(server->listen_socket6, &read_fds);
177 max_fd = server->listen_socket6 > max_fd ? server->listen_socket6 : max_fd;
178 }
179
180 // Use timeout from config (defaults to 1 second if not set)
181 int timeout_sec = server->config.accept_timeout_sec > 0 ? server->config.accept_timeout_sec : 1;
182 struct timeval timeout = {.tv_sec = timeout_sec, .tv_usec = 0};
183
184 int select_result = socket_select((int)(max_fd + 1), &read_fds, NULL, NULL, &timeout);
185
186 if (select_result < 0) {
187 // Check if interrupted by signal (expected during shutdown)
188 int err = socket_get_last_error();
189 if (err == EINTR) {
190 // Signal interrupt (e.g., SIGTERM, SIGINT) - check running flag and continue
191 log_debug("select() interrupted by signal");
192 continue;
193 }
194 // Actual error - socket_get_error_string() returns last socket error
195 log_error("select() failed in accept loop: %s (errno=%d)", socket_get_error_string(), err);
196 continue;
197 }
198
199 if (select_result == 0) {
200 // Timeout - check running flag and continue
201 continue;
202 }
203
204 // Check which socket has incoming connection
205 socket_t ready_socket = INVALID_SOCKET_VALUE;
206 if (server->listen_socket != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket, &read_fds)) {
207 ready_socket = server->listen_socket;
208 } else if (server->listen_socket6 != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket6, &read_fds)) {
209 ready_socket = server->listen_socket6;
210 }
211
212 if (ready_socket == INVALID_SOCKET_VALUE) {
213 // Spurious wakeup
214 continue;
215 }
216
217 // Accept connection
218 struct sockaddr_storage client_addr;
219 socklen_t client_addr_len = sizeof(client_addr);
220 socket_t client_socket = accept(ready_socket, (struct sockaddr *)&client_addr, &client_addr_len);
221
222 if (client_socket == INVALID_SOCKET_VALUE) {
223 log_warn("Failed to accept connection");
224 continue;
225 }
226
227 // Format client IP for logging
228 char client_ip[INET6_ADDRSTRLEN];
229 int addr_family = (client_addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
230 if (format_ip_address(addr_family, (struct sockaddr *)&client_addr, client_ip, sizeof(client_ip)) != ASCIICHAT_OK) {
231 SAFE_STRNCPY(client_ip, "(unknown)", sizeof(client_ip));
232 }
233
234 log_info("Accepted connection from %s", client_ip);
235
236 // Allocate client context
238 if (!ctx) {
239 log_error("Failed to allocate client context");
240 socket_close(client_socket);
241 continue;
242 }
243
244 ctx->client_socket = client_socket;
245 ctx->addr = client_addr;
246 ctx->addr_len = client_addr_len;
247 ctx->user_data = server->config.user_data;
248
249 // Spawn client handler thread
250 // Handler is responsible for:
251 // 1. Allocating client_data
252 // 2. Calling tcp_server_add_client() to register
253 // 3. Spawning additional worker threads via tcp_server_spawn_thread() if needed
254 // 4. Processing client requests
255 // 5. Calling tcp_server_remove_client() on disconnect
256 // 6. Closing socket and freeing ctx
257 asciichat_thread_t thread;
258 if (asciichat_thread_create(&thread, server->config.client_handler, ctx) != 0) {
259 log_error("Failed to create client handler thread for %s", client_ip);
260 SAFE_FREE(ctx);
261 socket_close(client_socket);
262 continue;
263 }
264
265 // Thread is detached (handler is responsible for cleanup)
266 (void)thread; // Suppress unused warning
267 }
268
269 log_info("TCP server accept loop exited");
270 return ASCIICHAT_OK;
271}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
void socket_fd_zero(fd_set *set)
Clear an fd_set.
#define EINTR
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.
pthread_t asciichat_thread_t
Thread handle type (POSIX: pthread_t)
int asciichat_thread_create(asciichat_thread_t *thread, void *(*func)(void *), void *arg)
Create a new thread.
Per-client connection context.
socket_t client_socket
Client connection socket.
socklen_t addr_len
Address length.
void * user_data
User-provided data from config.
void * user_data
User data passed to each client handler.
tcp_client_handler_fn client_handler
Client handler callback.
int accept_timeout_sec
select() timeout in seconds (for responsive shutdown)

References tcp_server_config_t::accept_timeout_sec, tcp_client_context_t::addr, tcp_client_context_t::addr_len, ASCIICHAT_OK, asciichat_thread_create(), 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_error, log_info, log_warn, 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_select(), tcp_client_context_t::user_data, and tcp_server_config_t::user_data.

Referenced by acds_server_run(), and server_main().

◆ 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 330 of file lib/network/tcp/server.c.

330 {
331 if (!server) {
332 return;
333 }
334 server->cleanup_fn = cleanup_fn;
335}

References tcp_server::cleanup_fn.

◆ tcp_server_shutdown()

void tcp_server_shutdown ( 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 273 of file lib/network/tcp/server.c.

273 {
274 if (!server) {
275 return;
276 }
277
278 log_info("Shutting down TCP server...");
279
280 // Signal server to stop
281 atomic_store(&server->running, false);
282
283 // Close listen sockets
284 if (server->listen_socket != INVALID_SOCKET_VALUE) {
285 log_debug("Closing IPv4 listen socket");
288 }
289
290 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
291 log_debug("Closing IPv6 listen socket");
294 }
295
296 // Clean up client registry
297 mutex_lock(&server->clients_mutex);
298
299 tcp_client_entry_t *entry, *tmp;
300 HASH_ITER(hh, server->clients, entry, tmp) {
301 // Call cleanup callback if set
302 if (server->cleanup_fn && entry->client_data) {
303 server->cleanup_fn(entry->client_data);
304 }
305
306 // Destroy thread pool (stops all threads and frees resources)
307 if (entry->threads) {
309 entry->threads = NULL;
310 }
311
312 HASH_DEL(server->clients, entry);
313 SAFE_FREE(entry);
314 }
315 server->clients = NULL;
316
317 mutex_unlock(&server->clients_mutex);
319
320 // Note: This function does NOT wait for client threads to exit
321 // Caller is responsible for thread lifecycle management
322
323 log_info("TCP server shutdown complete");
324}
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.

References tcp_server::cleanup_fn, tcp_client_entry::client_data, tcp_server::clients, tcp_server::clients_mutex, INVALID_SOCKET_VALUE, tcp_server::listen_socket, tcp_server::listen_socket6, log_debug, log_info, mutex_destroy(), mutex_lock, mutex_unlock, tcp_server::running, SAFE_FREE, socket_close(), thread_pool_destroy(), and tcp_client_entry::threads.

Referenced by acds_server_init(), acds_server_shutdown(), and server_main().

◆ 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 521 of file lib/network/tcp/server.c.

522 {
523 if (!server || !thread_func) {
524 return SET_ERRNO(ERROR_INVALID_PARAM, "server or thread_func is NULL");
525 }
526
527 if (client_socket == INVALID_SOCKET_VALUE) {
528 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
529 }
530
531 // Find client entry
532 mutex_lock(&server->clients_mutex);
533 tcp_client_entry_t *entry = NULL;
534 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
535
536 if (!entry) {
537 mutex_unlock(&server->clients_mutex);
538 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
539 }
540
541 // Spawn thread in client's thread pool
542 asciichat_error_t result = thread_pool_spawn(entry->threads, thread_func, thread_arg, stop_id, thread_name);
543
544 mutex_unlock(&server->clients_mutex);
545
546 if (result != ASCIICHAT_OK) {
547 return result;
548 }
549
550 size_t thread_count = thread_pool_get_count(entry->threads);
551 log_debug("Spawned thread '%s' (stop_id=%d) for client socket=%d (total_threads=%zu)",
552 thread_name ? thread_name : "unnamed", stop_id, client_socket, thread_count);
553
554 return ASCIICHAT_OK;
555}
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
Definition thread_pool.c:65

References ASCIICHAT_OK, tcp_server::clients, tcp_server::clients_mutex, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, log_debug, mutex_lock, mutex_unlock, 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 557 of file lib/network/tcp/server.c.

557 {
558 if (!server) {
559 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
560 }
561
562 if (client_socket == INVALID_SOCKET_VALUE) {
563 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
564 }
565
566 // Find client entry
567 mutex_lock(&server->clients_mutex);
568 tcp_client_entry_t *entry = NULL;
569 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
570
571 if (!entry) {
572 mutex_unlock(&server->clients_mutex);
573 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
574 }
575
576 // Stop all threads in client's thread pool (in stop_id order)
578 if (entry->threads) {
579 result = thread_pool_stop_all(entry->threads);
580 }
581
582 mutex_unlock(&server->clients_mutex);
583
584 log_debug("All threads stopped for client socket=%d", client_socket);
585 return result;
586}
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.

References ASCIICHAT_OK, tcp_server::clients, tcp_server::clients_mutex, ERROR_INVALID_PARAM, ERROR_NOT_FOUND, INVALID_SOCKET_VALUE, log_debug, mutex_lock, mutex_unlock, SET_ERRNO, thread_pool_stop_all(), and tcp_client_entry::threads.