ascii-chat 0.8.38
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 <errno.h>
11
12#ifdef _WIN32
13#include <winsock2.h>
14#include <ws2tcpip.h>
15#else
16#include <netdb.h>
17#endif
18
19#include <ascii-chat/network/tcp/server.h>
20#include <ascii-chat/common.h>
21#include <ascii-chat/log/logging.h>
22#include <ascii-chat/platform/socket.h>
23#include <ascii-chat/platform/thread.h>
24#include <ascii-chat/thread_pool.h>
25#include <ascii-chat/util/ip.h>
26#include <ascii-chat/util/time.h>
27
39static socket_t bind_and_listen(const char *address, int family, int port) {
40 struct addrinfo hints, *res = NULL;
41 memset(&hints, 0, sizeof(hints));
42 hints.ai_family = family;
43 hints.ai_socktype = SOCK_STREAM;
44 hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
45
46 char port_str[16];
47 SAFE_SNPRINTF(port_str, sizeof(port_str), "%d", port);
48
49 // Use provided address or NULL for wildcard
50 const char *addr_str = (address && address[0] != '\0') ? address : NULL;
51
52 int gai_result = getaddrinfo(addr_str, port_str, &hints, &res);
53 if (gai_result != 0) {
54 log_error("getaddrinfo failed for %s:%d: %s", addr_str ? addr_str : "(wildcard)", port, gai_strerror(gai_result));
55 return INVALID_SOCKET_VALUE;
56 }
57
58 socket_t server_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
59 if (server_socket == INVALID_SOCKET_VALUE) {
60 log_error("Failed to create socket for %s:%d", addr_str ? addr_str : "(wildcard)", port);
61 freeaddrinfo(res);
62 return INVALID_SOCKET_VALUE;
63 }
64
65 // Enable SO_REUSEADDR to allow quick restarts
66 int reuse = 1;
67 if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&reuse, sizeof(reuse)) < 0) {
68 log_warn("Failed to set SO_REUSEADDR on %s:%d", addr_str ? addr_str : "(wildcard)", port);
69 }
70
71 // For IPv6, disable IPv4-mapped addresses (IPV6_V6ONLY=1) to support dual-stack
72 if (family == AF_INET6) {
73 int ipv6only = 1;
74 if (setsockopt(server_socket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6only, sizeof(ipv6only)) < 0) {
75 log_warn("Failed to set IPV6_V6ONLY on [%s]:%d", addr_str ? addr_str : "::", port);
76 }
77 }
78
79 // Bind socket
80 if (bind(server_socket, res->ai_addr, (socklen_t)res->ai_addrlen) < 0) {
81 log_error("Failed to bind %s:%d", addr_str ? addr_str : "(wildcard)", port);
82 socket_close(server_socket);
83 freeaddrinfo(res);
84 return INVALID_SOCKET_VALUE;
85 }
86
87 freeaddrinfo(res);
88
89 // Listen with backlog of 128 connections
90 if (listen(server_socket, 128) < 0) {
91 log_error("Failed to listen on %s:%d", addr_str ? addr_str : "(wildcard)", port);
92 socket_close(server_socket);
93 return INVALID_SOCKET_VALUE;
94 }
95
96 log_info("Listening on %s%s%s:%d (%s)", family == AF_INET6 ? "[" : "",
97 addr_str ? addr_str : (family == AF_INET ? "0.0.0.0" : "::"), family == AF_INET6 ? "]" : "", port,
98 family == AF_INET ? "IPv4" : "IPv6");
99
100 return server_socket;
101}
102
103asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config) {
104 if (!server || !config) {
105 return SET_ERRNO(ERROR_INVALID_PARAM, "server or config is NULL");
106 }
107
108 // Note: client_handler is optional - some users may use tcp_server just for socket setup
109 // and implement their own accept loop (like ascii-chat server with its cleanup logic)
110
111 // Initialize server state
112 memset(server, 0, sizeof(*server));
113 server->listen_socket = INVALID_SOCKET_VALUE;
114 server->listen_socket6 = INVALID_SOCKET_VALUE;
115 atomic_store(&server->running, true);
116 server->config = *config; // Copy config
117
118 // Initialize client registry
119 server->clients = NULL; // uthash starts with NULL
120 server->cleanup_fn = NULL;
121 if (mutex_init(&server->clients_mutex) != 0) {
122 return SET_ERRNO(ERROR_THREAD, "Failed to initialize clients mutex");
123 }
124
125 // Determine which IP versions to bind
126 bool should_bind_ipv4 = config->bind_ipv4;
127 bool should_bind_ipv6 = config->bind_ipv6;
128
129 // Bind IPv4 socket if requested
130 if (should_bind_ipv4) {
131 const char *ipv4_addr = (config->ipv4_address && config->ipv4_address[0] != '\0') ? config->ipv4_address : NULL;
132 server->listen_socket = bind_and_listen(ipv4_addr, AF_INET, config->port);
133
134 if (server->listen_socket == INVALID_SOCKET_VALUE) {
135 log_warn("Failed to bind IPv4 socket");
136 }
137 }
138
139 // Bind IPv6 socket if requested
140 if (should_bind_ipv6) {
141 const char *ipv6_addr = (config->ipv6_address && config->ipv6_address[0] != '\0') ? config->ipv6_address : NULL;
142 server->listen_socket6 = bind_and_listen(ipv6_addr, AF_INET6, config->port);
143
144 if (server->listen_socket6 == INVALID_SOCKET_VALUE) {
145 log_warn("Failed to bind IPv6 socket");
146 }
147 }
148
149 // Ensure at least one socket bound successfully
150 if (server->listen_socket == INVALID_SOCKET_VALUE && server->listen_socket6 == INVALID_SOCKET_VALUE) {
151 return SET_ERRNO(ERROR_NETWORK_BIND, "Failed to bind any sockets (IPv4 and IPv6 both failed)");
152 }
153
154 return ASCIICHAT_OK;
155}
156
157asciichat_error_t tcp_server_run(tcp_server_t *server) {
158 if (!server) {
159 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
160 }
161
162 if (!server->config.client_handler) {
163 return SET_ERRNO(ERROR_INVALID_PARAM,
164 "client_handler is required for tcp_server_run() - use custom accept loop if handler is NULL");
165 }
166
167 log_debug("TCP server starting accept loop...");
168
169 while (atomic_load(&server->running)) {
170 // Build fd_set for select()
171 fd_set read_fds;
172 socket_fd_zero(&read_fds);
173 socket_t max_fd = 0;
174
175 // Add IPv4 socket if available
176 if (server->listen_socket != INVALID_SOCKET_VALUE) {
177 socket_fd_set(server->listen_socket, &read_fds);
178 max_fd = server->listen_socket > max_fd ? server->listen_socket : max_fd;
179 }
180
181 // Add IPv6 socket if available
182 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
183 socket_fd_set(server->listen_socket6, &read_fds);
184 max_fd = server->listen_socket6 > max_fd ? server->listen_socket6 : max_fd;
185 }
186
187 // Use timeout from config (defaults to 1 second if not set)
188 // Convert double seconds to tv_sec and tv_usec
189 double timeout_sec_double = server->config.accept_timeout_sec > 0 ? server->config.accept_timeout_sec : 1.0;
190 time_t timeout_sec = (time_t)timeout_sec_double;
191 long timeout_usec = (long)((timeout_sec_double - timeout_sec) * (double)US_PER_SEC_INT);
192 struct timeval timeout = {.tv_sec = timeout_sec, .tv_usec = timeout_usec};
193
194 int select_result = socket_select((int)(max_fd + 1), &read_fds, NULL, NULL, &timeout);
195
196 if (select_result < 0) {
197 // Check if interrupted by signal (expected during shutdown)
198 int err = socket_get_last_error();
199 if (err == EINTR) {
200 // Signal interrupt (e.g., SIGTERM, SIGINT) - check running flag and continue
201 log_debug("select() interrupted by signal");
202 continue;
203 }
204 // Actual error - socket_get_error_string() returns last socket error
205 log_error("select() failed in accept loop: %s (errno=%d)", socket_get_error_string(), err);
206 continue;
207 }
208
209 if (select_result == 0) {
210 // Timeout - invoke status update callback if configured
211 if (server->config.status_update_fn) {
212 server->config.status_update_fn(server->config.status_update_data);
213 }
214 continue;
215 }
216
217 // Check which socket has incoming connection
218 socket_t ready_socket = INVALID_SOCKET_VALUE;
219 if (server->listen_socket != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket, &read_fds)) {
220 ready_socket = server->listen_socket;
221 } else if (server->listen_socket6 != INVALID_SOCKET_VALUE && socket_fd_isset(server->listen_socket6, &read_fds)) {
222 ready_socket = server->listen_socket6;
223 }
224
225 if (ready_socket == INVALID_SOCKET_VALUE) {
226 // Spurious wakeup
227 continue;
228 }
229
230 // Accept connection
231 struct sockaddr_storage client_addr;
232 socklen_t client_addr_len = sizeof(client_addr);
233 socket_t client_socket = accept(ready_socket, (struct sockaddr *)&client_addr, &client_addr_len);
234
235 if (client_socket == INVALID_SOCKET_VALUE) {
236 log_warn("Failed to accept connection");
237 continue;
238 }
239
240 // Format client IP for logging
241 char client_ip[INET6_ADDRSTRLEN];
242 int addr_family = (client_addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
243 if (format_ip_address(addr_family, (struct sockaddr *)&client_addr, client_ip, sizeof(client_ip)) != ASCIICHAT_OK) {
244 SAFE_STRNCPY(client_ip, "(unknown)", sizeof(client_ip));
245 }
246
247 log_debug("Accepted connection from %s", client_ip);
248
249 // Allocate client context
250 tcp_client_context_t *ctx = SAFE_MALLOC(sizeof(tcp_client_context_t), tcp_client_context_t *);
251 if (!ctx) {
252 log_error("Failed to allocate client context");
253 socket_close(client_socket);
254 continue;
255 }
256
257 ctx->client_socket = client_socket;
258 ctx->addr = client_addr;
259 ctx->addr_len = client_addr_len;
260 ctx->user_data = server->config.user_data;
261
262 // Spawn client handler thread
263 // Handler is responsible for:
264 // 1. Allocating client_data
265 // 2. Calling tcp_server_add_client() to register
266 // 3. Spawning additional worker threads via tcp_server_spawn_thread() if needed
267 // 4. Processing client requests
268 // 5. Calling tcp_server_remove_client() on disconnect
269 // 6. Closing socket and freeing ctx
270 asciichat_thread_t thread;
271 if (asciichat_thread_create(&thread, server->config.client_handler, ctx) != 0) {
272 log_error("Failed to create client handler thread for %s", client_ip);
273 SAFE_FREE(ctx);
274 socket_close(client_socket);
275 continue;
276 }
277
278 // Thread is detached (handler is responsible for cleanup)
279 (void)thread; // Suppress unused warning
280 }
281
282 log_debug("TCP server accept loop exited");
283 return ASCIICHAT_OK;
284}
285
286void tcp_server_destroy(tcp_server_t *server) {
287 if (!server) {
288 return;
289 }
290
291 log_debug("Shutting down TCP server...");
292
293 // Signal server to stop
294 atomic_store(&server->running, false);
295
296 // Close listen sockets
297 if (server->listen_socket != INVALID_SOCKET_VALUE) {
298 log_debug("Closing IPv4 listen socket");
299 socket_close(server->listen_socket);
300 server->listen_socket = INVALID_SOCKET_VALUE;
301 }
302
303 if (server->listen_socket6 != INVALID_SOCKET_VALUE) {
304 log_debug("Closing IPv6 listen socket");
305 socket_close(server->listen_socket6);
306 server->listen_socket6 = INVALID_SOCKET_VALUE;
307 }
308
309 // Clean up client registry
310 mutex_lock(&server->clients_mutex);
311
312 tcp_client_entry_t *entry = NULL, *tmp = NULL;
313 HASH_ITER(hh, server->clients, entry, tmp) {
314 // Call cleanup callback if set
315 if (server->cleanup_fn && entry->client_data) {
316 server->cleanup_fn(entry->client_data);
317 }
318
319 // Destroy thread pool (stops all threads and frees resources)
320 if (entry->threads) {
321 thread_pool_destroy(entry->threads);
322 entry->threads = NULL;
323 }
324
325 HASH_DEL(server->clients, entry);
326 SAFE_FREE(entry);
327 }
328 server->clients = NULL;
329
330 mutex_unlock(&server->clients_mutex);
331 mutex_destroy(&server->clients_mutex);
332
333 // Note: This function does NOT wait for client threads to exit
334 // Caller is responsible for thread lifecycle management
335
336 log_debug("TCP server shutdown complete");
337}
338
339// ============================================================================
340// Client Management Functions
341// ============================================================================
342
343void tcp_server_set_cleanup_callback(tcp_server_t *server, tcp_client_cleanup_fn cleanup_fn) {
344 if (!server) {
345 return;
346 }
347 server->cleanup_fn = cleanup_fn;
348}
349
350asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data) {
351 if (!server) {
352 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
353 }
354
355 if (socket == INVALID_SOCKET_VALUE) {
356 return SET_ERRNO(ERROR_INVALID_PARAM, "socket is invalid");
357 }
358
359 // Allocate new entry
360 tcp_client_entry_t *entry = SAFE_MALLOC(sizeof(tcp_client_entry_t), tcp_client_entry_t *);
361 if (!entry) {
362 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate client entry");
363 }
364
365 entry->socket = socket;
366 entry->client_data = client_data;
367
368 // Create thread pool for this client
369 char pool_name[64];
370 SAFE_SNPRINTF(pool_name, sizeof(pool_name), "client_%d", socket);
371 entry->threads = thread_pool_create(pool_name);
372 if (!entry->threads) {
373 SAFE_FREE(entry);
374 return SET_ERRNO(ERROR_MEMORY, "Failed to create thread pool for client");
375 }
376
377 // Add to hash table (thread-safe)
378 mutex_lock(&server->clients_mutex);
379 HASH_ADD(hh, server->clients, socket, sizeof(socket_t), entry);
380 mutex_unlock(&server->clients_mutex);
381
382 log_debug("Added client socket=%d to registry", socket);
383 return ASCIICHAT_OK;
384}
385
386asciichat_error_t tcp_server_remove_client(tcp_server_t *server, socket_t socket) {
387 if (!server) {
388 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
389 }
390
391 mutex_lock(&server->clients_mutex);
392
393 tcp_client_entry_t *entry = NULL;
394 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
395
396 if (!entry) {
397 mutex_unlock(&server->clients_mutex);
398 // Already removed (e.g., during shutdown) - this is fine
399 log_debug("Client socket=%d already removed from registry", socket);
400 return ASCIICHAT_OK;
401 }
402
403 // Call cleanup callback if set
404 if (server->cleanup_fn && entry->client_data) {
405 server->cleanup_fn(entry->client_data);
406 }
407
408 // Destroy thread pool (stops all threads and frees resources)
409 if (entry->threads) {
410 thread_pool_destroy(entry->threads);
411 entry->threads = NULL;
412 }
413
414 HASH_DEL(server->clients, entry);
415 SAFE_FREE(entry);
416
417 mutex_unlock(&server->clients_mutex);
418
419 log_debug("Removed client socket=%d from registry", socket);
420 return ASCIICHAT_OK;
421}
422
423asciichat_error_t tcp_server_get_client(tcp_server_t *server, socket_t socket, void **out_data) {
424 if (!server || !out_data) {
425 return SET_ERRNO(ERROR_INVALID_PARAM, "server or out_data is NULL");
426 }
427
428 mutex_lock(&server->clients_mutex);
429
430 tcp_client_entry_t *entry = NULL;
431 HASH_FIND(hh, server->clients, &socket, sizeof(socket_t), entry);
432
433 if (!entry) {
434 *out_data = NULL;
435 mutex_unlock(&server->clients_mutex);
436 return SET_ERRNO(ERROR_INVALID_STATE, "Client socket=%d not in registry", socket);
437 }
438
439 *out_data = entry->client_data;
440 mutex_unlock(&server->clients_mutex);
441
442 return ASCIICHAT_OK;
443}
444
445void tcp_server_foreach_client(tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg) {
446 if (!server || !callback) {
447 return;
448 }
449
450 mutex_lock(&server->clients_mutex);
451
452 tcp_client_entry_t *entry, *tmp;
453 HASH_ITER(hh, server->clients, entry, tmp) {
454 callback(entry->socket, entry->client_data, user_arg);
455 }
456
457 mutex_unlock(&server->clients_mutex);
458}
459
460size_t tcp_server_get_client_count(tcp_server_t *server) {
461 if (!server) {
462 return 0;
463 }
464
465 mutex_lock(&server->clients_mutex);
466 size_t count = HASH_COUNT(server->clients);
467 mutex_unlock(&server->clients_mutex);
468
469 return count;
470}
471
472// ============================================================================
473// Client Context Utilities
474// ============================================================================
475
476const char *tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len) {
477 if (!ctx) {
478 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
479 return NULL;
480 }
481 if (!buf) {
482 SET_ERRNO(ERROR_INVALID_PARAM, "buf is NULL");
483 return NULL;
484 }
485 if (len == 0) {
486 SET_ERRNO(ERROR_INVALID_PARAM, "len is 0");
487 return NULL;
488 }
489
490 // Determine address family
491 int addr_family = (ctx->addr.ss_family == AF_INET) ? AF_INET : AF_INET6;
492
493 // Format IP address using existing utility
494 if (format_ip_address(addr_family, (struct sockaddr *)&ctx->addr, buf, len) != 0) {
495 return NULL;
496 }
497
498 return buf;
499}
500
501int tcp_client_context_get_port(const tcp_client_context_t *ctx) {
502 if (!ctx) {
503 SET_ERRNO(ERROR_INVALID_PARAM, "ctx is NULL");
504 return -1;
505 }
506
507 // Extract port based on address family
508 if (ctx->addr.ss_family == AF_INET) {
509 struct sockaddr_in *addr_in = (struct sockaddr_in *)&ctx->addr;
510 return ntohs(addr_in->sin_port);
511 } else if (ctx->addr.ss_family == AF_INET6) {
512 struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&ctx->addr;
513 return ntohs(addr_in6->sin6_port);
514 }
515
516 SET_ERRNO(ERROR_INVALID_STATE, "Unknown address family: %d", ctx->addr.ss_family);
517 return -1;
518}
519
520void tcp_server_reject_client(socket_t socket, const char *reason) {
521 if (socket == INVALID_SOCKET_VALUE) {
522 SET_ERRNO(ERROR_INVALID_PARAM, "socket is INVALID_SOCKET_VALUE");
523 return;
524 }
525
526 log_warn("Rejecting client connection: %s", reason ? reason : "unknown reason");
527 socket_close(socket);
528}
529
530// ============================================================================
531// Client Thread Pool Management
532// ============================================================================
533
534asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *),
535 void *thread_arg, int stop_id, const char *thread_name) {
536 if (!server || !thread_func) {
537 return SET_ERRNO(ERROR_INVALID_PARAM, "server or thread_func is NULL");
538 }
539
540 // For WebRTC clients (no socket): create thread directly without TCP server thread pool tracking
541 // This allows render threads to work for both TCP and WebRTC clients
542 if (client_socket == INVALID_SOCKET_VALUE) {
543 // Extract thread handle from thread_arg if it's a client_info_t
544 // We need to store the thread handle somewhere, but for now just create the thread
545 // The caller must manage the thread handle directly for WebRTC clients
546 log_debug("Spawning standalone thread '%s' (no socket, WebRTC client)", thread_name ? thread_name : "unnamed");
547
548 // For WebRTC clients, we can't use thread_pool since there's no socket entry
549 // Instead, create the thread directly - the caller must handle the thread
550 // This is a hack to allow reusing tcp_server_spawn_thread for WebRTC
551 // In the future, consider a unified thread management system
552 asciichat_thread_t temp_thread;
553 asciichat_error_t result = asciichat_thread_create(&temp_thread, thread_func, thread_arg);
554 if (result != ASCIICHAT_OK) {
555 return result;
556 }
557
558 // Note: temp_thread handle is lost here, but the thread is running
559 // The caller must manage thread lifecycle for WebRTC clients differently
560 (void)temp_thread; // Suppress unused warning
561 return ASCIICHAT_OK;
562 }
563
564 // Find client entry
565 mutex_lock(&server->clients_mutex);
566 tcp_client_entry_t *entry = NULL;
567 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
568
569 if (!entry) {
570 mutex_unlock(&server->clients_mutex);
571 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
572 }
573
574 // Spawn thread in client's thread pool
575 asciichat_error_t result = thread_pool_spawn(entry->threads, thread_func, thread_arg, stop_id, thread_name);
576
577 mutex_unlock(&server->clients_mutex);
578
579 if (result != ASCIICHAT_OK) {
580 return result;
581 }
582
583 size_t thread_count = thread_pool_get_count(entry->threads);
584 log_debug("Spawned thread '%s' (stop_id=%d) for client socket=%d (total_threads=%zu)",
585 thread_name ? thread_name : "unnamed", stop_id, client_socket, thread_count);
586
587 return ASCIICHAT_OK;
588}
589
590asciichat_error_t tcp_server_stop_client_threads(tcp_server_t *server, socket_t client_socket) {
591 if (!server) {
592 return SET_ERRNO(ERROR_INVALID_PARAM, "server is NULL");
593 }
594
595 if (client_socket == INVALID_SOCKET_VALUE) {
596 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
597 }
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 // Stop all threads in client's thread pool (in stop_id order)
610 asciichat_error_t result = ASCIICHAT_OK;
611 if (entry->threads) {
612 result = thread_pool_stop_all(entry->threads);
613 }
614
615 mutex_unlock(&server->clients_mutex);
616
617 log_debug("All threads stopped for client socket=%d", client_socket);
618 return result;
619}
620
621asciichat_error_t tcp_server_get_thread_count(tcp_server_t *server, socket_t client_socket, size_t *count) {
622 if (!server || !count) {
623 return SET_ERRNO(ERROR_INVALID_PARAM, "server or count is NULL");
624 }
625
626 if (client_socket == INVALID_SOCKET_VALUE) {
627 return SET_ERRNO(ERROR_INVALID_PARAM, "client_socket is invalid");
628 }
629
630 *count = 0;
631
632 // Find client entry
633 mutex_lock(&server->clients_mutex);
634 tcp_client_entry_t *entry = NULL;
635 HASH_FIND(hh, server->clients, &client_socket, sizeof(socket_t), entry);
636
637 if (!entry) {
638 mutex_unlock(&server->clients_mutex);
639 return SET_ERRNO(ERROR_NOT_FOUND, "Client socket=%d not in registry", client_socket);
640 }
641
642 // Get thread count from pool
643 if (entry->threads) {
644 *count = thread_pool_get_count(entry->threads);
645 }
646
647 mutex_unlock(&server->clients_mutex);
648
649 return ASCIICHAT_OK;
650}
int socket_t
asciichat_error_t format_ip_address(int family, const struct sockaddr *addr, char *output, size_t output_size)
Definition ip.c:196
void tcp_server_destroy(tcp_server_t *server)
void tcp_server_set_cleanup_callback(tcp_server_t *server, tcp_client_cleanup_fn cleanup_fn)
void tcp_server_reject_client(socket_t socket, const char *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)
asciichat_error_t tcp_server_remove_client(tcp_server_t *server, socket_t socket)
void tcp_server_foreach_client(tcp_server_t *server, tcp_client_foreach_fn callback, void *user_arg)
asciichat_error_t tcp_server_stop_client_threads(tcp_server_t *server, socket_t client_socket)
asciichat_error_t tcp_server_init(tcp_server_t *server, const tcp_server_config_t *config)
asciichat_error_t tcp_server_get_client(tcp_server_t *server, socket_t socket, void **out_data)
int tcp_client_context_get_port(const tcp_client_context_t *ctx)
asciichat_error_t tcp_server_run(tcp_server_t *server)
asciichat_error_t tcp_server_get_thread_count(tcp_server_t *server, socket_t client_socket, size_t *count)
const char * tcp_client_context_get_ip(const tcp_client_context_t *ctx, char *buf, size_t len)
size_t tcp_server_get_client_count(tcp_server_t *server)
asciichat_error_t tcp_server_add_client(tcp_server_t *server, socket_t socket, void *client_data)
void thread_pool_destroy(thread_pool_t *pool)
Definition thread_pool.c:48
thread_pool_t * thread_pool_create(const char *pool_name)
Definition thread_pool.c:17
size_t thread_pool_get_count(const thread_pool_t *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)
Definition thread_pool.c:70
asciichat_error_t thread_pool_stop_all(thread_pool_t *pool)
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21