ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
lib/network/tcp/server.c
Go to the documentation of this file.
1
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <stdbool.h>
10#include <netdb.h>
11#include <errno.h>
12
13#include "network/tcp/server.h"
14#include "common.h"
15#include "log/logging.h"
16#include "platform/socket.h"
17#include "platform/thread.h"
18#include "thread_pool.h"
19#include "util/ip.h"
20
32static socket_t bind_and_listen(const char *address, int family, int port) {
33 struct addrinfo hints, *res = NULL;
34 memset(&hints, 0, sizeof(hints));
35 hints.ai_family = family;
36 hints.ai_socktype = SOCK_STREAM;
37 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
38
39 char port_str[16];
40 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
41
42 // Use provided address or NULL for wildcard
43 const char *addr_str = (address && address[0] != '\0') ? address : NULL;
44
45 int gai_result = getaddrinfo(addr_str, port_str, &hints, &res);
46 if (gai_result != 0) {
47 log_error("getaddrinfo failed for %s:%d: %s", addr_str ? addr_str : "(wildcard)", port, gai_strerror(gai_result));
49 }
50
51 socket_t server_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
52 if (server_socket == INVALID_SOCKET_VALUE) {
53 log_error("Failed to create socket for %s:%d", addr_str ? addr_str : "(wildcard)", port);
54 freeaddrinfo(res);
56 }
57
58 // Enable SO_REUSEADDR to allow quick restarts
59 int reuse = 1;
60 if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
61 log_warn("Failed to set SO_REUSEADDR on %s:%d", addr_str ? addr_str : "(wildcard)", port);
62 }
63
64 // For IPv6, disable IPv4-mapped addresses (IPV6_V6ONLY=1) to support dual-stack
65 if (family == AF_INET6) {
66 int ipv6only = 1;
67 if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6only, sizeof(ipv6only)) < 0) {
68 log_warn("Failed to set IPV6_V6ONLY on [%s]:%d", addr_str ? addr_str : "::", port);
69 }
70 }
71
72 // Bind socket
73 if (bind(server_socket, res->ai_addr, (socklen_t)res->ai_addrlen) < 0) {
74 log_error("Failed to bind %s:%d", addr_str ? addr_str : "(wildcard)", port);
75 socket_close(server_socket);
76 freeaddrinfo(res);
78 }
79
80 freeaddrinfo(res);
81
82 // Listen with backlog of 128 connections
83 if (listen(server_socket, 128) < 0) {
84 log_error("Failed to listen on %s:%d", addr_str ? addr_str : "(wildcard)", port);
85 socket_close(server_socket);
87 }
88
89 log_info("Listening on %s%s%s:%d (%s)", family == AF_INET6 ? "[" : "",
90 addr_str ? addr_str : (family == AF_INET ? "0.0.0.0" : "::"), family == AF_INET6 ? "]" : "", port,
91 family == AF_INET ? "IPv4" : "IPv6");
92
93 return server_socket;
94}
95
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}
149
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}
272
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}
325
326// ============================================================================
327// Client Management Functions
328// ============================================================================
329
331 if (!server) {
332 return;
333 }
334 server->cleanup_fn = cleanup_fn;
335}
336
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}
372
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}
409
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}
431
432void tcp_server_foreach_client(tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg) {
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}
446
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}
458
459// ============================================================================
460// Client Context Utilities
461// ============================================================================
462
463const char *tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len) {
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}
487
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}
506
507void tcp_server_reject_client(socket_t socket, const char *reason) {
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}
516
517// ============================================================================
518// Client Thread Pool Management
519// ============================================================================
520
521asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *),
522 void *thread_arg, int stop_id, const char *thread_name) {
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}
556
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}
587
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}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#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
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_NETWORK_BIND
Definition error_codes.h:70
@ ERROR_NOT_FOUND
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
Definition error_codes.h:95
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
void socket_fd_zero(fd_set *set)
Clear an fd_set.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
#define EINTR
#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
void socket_fd_set(socket_t sock, fd_set *set)
Add a socket to an fd_set.
int socket_close(socket_t sock)
Close a socket.
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.
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
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.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
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
๐ŸŒ IP Address Parsing and Formatting Utilities
void tcp_server_set_cleanup_callback(tcp_server_t *server, tcp_client_cleanup_fn cleanup_fn)
Set client cleanup callback.
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_remove_client(tcp_server_t *server, socket_t socket)
Remove client from registry.
void tcp_server_foreach_client(tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg)
Iterate over all clients.
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_init(tcp_server_t *server, const tcp_server_config_t *config)
Initialize TCP server.
asciichat_error_t tcp_server_get_client(tcp_server_t *server, socket_t socket, void **out_data)
Get client data.
int tcp_client_context_get_port(const tcp_client_context_t *ctx)
Get port number from client context.
asciichat_error_t tcp_server_run(tcp_server_t *server)
Run TCP server accept loop.
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.
void tcp_server_shutdown(tcp_server_t *server)
Shutdown TCP server.
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.
size_t tcp_server_get_client_count(tcp_server_t *server)
Get client count.
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
Add client to registry.
void(* tcp_client_foreach_fn)(socket_t socket, void *client_data, void *user_arg)
Callback for iterating over clients.
void(* tcp_client_cleanup_fn)(void *client_data)
Callback for client cleanup.
๐Ÿ“ Logging API with multiple log levels and terminal output control
๐Ÿงต Cross-platform thread interface for ascii-chat
Cross-platform socket interface for ascii-chat.
Per-client connection context.
socket_t client_socket
Client connection socket.
socklen_t addr_len
Address length.
struct sockaddr_storage addr
Client address.
void * user_data
User-provided data from config.
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 server configuration.
bool bind_ipv6
Whether to bind IPv6 socket.
void * user_data
User data passed to each client handler.
tcp_client_handler_fn client_handler
Client handler callback.
bool bind_ipv4
Whether to bind IPv4 socket.
const char * ipv4_address
IPv4 bind address (NULL or empty = don't bind)
int accept_timeout_sec
select() timeout in seconds (for responsive shutdown)
const char * ipv6_address
IPv6 bind address (NULL or empty = don't bind)
TCP server state.
mutex_t clients_mutex
Mutex protecting client registry.
atomic_bool running
Server running flag (set false to shutdown)
tcp_server_config_t config
Server configuration.
tcp_client_entry_t * clients
Hash table of connected clients.
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.
Definition thread_pool.c:43
thread_pool_t * thread_pool_create(const char *pool_name)
Create a new thread pool.
Definition thread_pool.c:12
size_t thread_pool_get_count(const thread_pool_t *pool)
Get thread count in the pool.
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
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
Stop all threads in the pool in stop_id order.
๐Ÿงต Generic thread pool abstraction for managing worker threads