#include #include /* ================================================================================================================== */ /* Concrete Implementation: Refactored Cross-Platform select() Driver Subclass Context */ typedef struct { c_TcpReactor_t base; c_socket_t* sockets; /* Array tracking all registered socket handles (Listener + Clients) */ c_size_t count; /* Current count of monitored sockets */ c_size_t capacity; /* Upper bounds allocation capacity limit */ c_Allocator_t allocator; /* Deep copy of the user-provided allocator */ } c_SelectReactorImpl_t; static c_err_t Select_Add(c_TcpReactor_t* self, c_socket_t sock, uint32_t events) { c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; (void)events; /* Enforce hard ceiling compliance checks against maximum threshold allowances for select */ if (impl->count >= FD_SETSIZE) { return C_ERR_OUTOFBOUND; } if (impl->count >= impl->capacity) { c_size_t old_cap = impl->capacity; c_size_t new_cap = old_cap == 0 ? 16 : C_MIN(old_cap * 2, FD_SETSIZE); c_socket_t* new_sockets = (c_socket_t*)c_Allocator_Realloc( &impl->allocator, impl->sockets, old_cap * sizeof(c_socket_t), new_cap * sizeof(c_socket_t) ); if (!new_sockets) return C_ERR_NOMEM; impl->sockets = new_sockets; impl->capacity = new_cap; } /* Prevent duplicate entries inside the tracking index */ for (c_size_t i = 0; i < impl->count; ++i) { if (impl->sockets[i] == sock) return C_SUCCESS; } impl->sockets[impl->count++] = sock; return C_SUCCESS; } static c_err_t Select_Remove(c_TcpReactor_t* self, c_socket_t sock) { c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; for (c_size_t i = 0; i < impl->count; ++i) { if (impl->sockets[i] == sock) { impl->sockets[i] = impl->sockets[impl->count - 1]; /* O(1) tail-swap optimization */ impl->count--; return C_SUCCESS; } } return C_ERR_NOTFOUND; } static c_err_t Select_Poll(c_TcpReactor_t* self, long timeout_ms, c_TcpServer_t* server, void* args) { c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; if (impl->count == 0 || !server->is_running) return C_SUCCESS; fd_set read_set; fd_set err_set; FD_ZERO(&read_set); FD_ZERO(&err_set); c_socket_t max_fd = 0; for (c_size_t i = 0; i < impl->count; ++i) { FD_SET(impl->sockets[i], &read_set); FD_SET(impl->sockets[i], &err_set); #if !defined(_WIN32) && !defined(_WIN64) if (impl->sockets[i] > max_fd) { max_fd = impl->sockets[i]; } #endif } struct timeval tv; struct timeval* tv_ptr = NULL; if (timeout_ms >= 0) { tv.tv_sec = timeout_ms / 1000; tv.tv_usec = (timeout_ms % 1000) * 1000; tv_ptr = &tv; } #if defined(_WIN32) || defined(_WIN64) int ret = select(0, &read_set, NULL, &err_set, tv_ptr); #else int ret = select((int)(max_fd + 1), &read_set, NULL, &err_set, tv_ptr); #endif if (ret < 0) { if (server->fnOnError) { server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLL, args); } return C_SUCCESS; } if (ret == 0) return C_SUCCESS; /* Timeout pass, exit early */ /* * Safe Backward Traversal Pass: * Scanning the tracked sockets pool backward ensures that deleting an element via * tail-swapping doesn't corrupt unvisited index boundaries during the current pass. */ for (c_size_t i = impl->count; i > 0; --i) { c_size_t idx = i - 1; c_socket_t active_sock = impl->sockets[idx]; c_bool_t has_read = FD_ISSET(active_sock, &read_set); c_bool_t has_error = FD_ISSET(active_sock, &err_set); if (!has_read && !has_error) continue; /* Channel A: Master server listener handles events */ if (active_sock == server->listen_sock) { if (has_read) { c_SockAddr_t peer_addr; c_socket_t client = c_SocketUtil_Accept(server->listen_sock, &peer_addr); if (c_Socket_IsValid(client)) { bool keep = true; if (server->fnOnConnect) { keep = server->fnOnConnect(server, client, &peer_addr, args); } if (keep) { Select_Add(self, client, 1); /* Add client onto select watchlist */ } else { c_Socket_Close(client); } } else if (server->fnOnError) { server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_ACCEPT, args); } } if (has_error) { if (server->fnOnError) { server->fnOnError(server, server->listen_sock, C_TCPSERVER_ERR_ON_POLLERR, args); } } ret--; } /* Channel B: Connected active client descriptor processing */ else { c_bool_t keep_alive = C_TRUE; /* Intercept operational error signals first */ if (has_error) { if (server->fnOnError) { server->fnOnError(server, active_sock, C_TCPSERVER_ERR_ON_POLLERR, args); } keep_alive = C_FALSE; } if (keep_alive && has_read) { /* Intercept silent socket termination conditions using zero-byte peeks */ char peek_buf; #if defined(_WIN32) || defined(_WIN64) int peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK); #else ssize_t peek_res = recv(active_sock, &peek_buf, 1, MSG_PEEK); #endif if (peek_res == 0 || peek_res == C_SOCKET_ERROR) { keep_alive = C_FALSE; /* Connection broken or closed by remote peer node */ } else if (server->fnOnRequest) { /* Fire your exact matching signature callback layout */ keep_alive = server->fnOnRequest(server, active_sock, args); } } /* Clean up active client descriptor arrays if data pipeline drops or errors out */ if (!keep_alive) { Select_Remove(self, active_sock); /* Fire your formal on_disconnect registration interface hook */ if (server->fnOnDisconnect) { server->fnOnDisconnect(server, active_sock, args); } c_Socket_Close(active_sock); } ret--; } if (ret == 0) break; /* Event counters satisfied, return early */ } return C_SUCCESS; } static void Select_Destroy(c_TcpReactor_t* self) { c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)self; c_Allocator_t alloc = impl->allocator; /* Safely dismantle any hanging active client sockets remaining in the array pool */ for (c_size_t i = 0; i < impl->count; ++i) { c_socket_t sock = impl->sockets[i]; c_Socket_Close(sock); } if (impl->sockets) c_Allocator_Free(&alloc, impl->sockets); c_Allocator_Free(&alloc, impl); } static const c_TcpReactorVtbl_t g_SelectReactorVtbl = { Select_Add, Select_Remove, Select_Poll, Select_Destroy }; /* ------------------------------------------------------------------------------------------------------------------ */ /* */ c_err_t c_TcpSelectReactor_Create(c_TcpReactor_t** out_reactor, c_Allocator_t* allocator) { if (!out_reactor) return C_ERR_PARAM; c_Allocator_t alloc = allocator ? *allocator : c_DefaultAllocator; c_SelectReactorImpl_t* impl = (c_SelectReactorImpl_t*)c_Allocator_Calloc(&alloc, 1, sizeof(c_SelectReactorImpl_t)); if (!impl) return C_ERR_NOMEM; impl->base.vtbl = &g_SelectReactorVtbl; impl->sockets = NULL; impl->count = 0; impl->capacity = 0; impl->allocator = alloc; *out_reactor = &impl->base; return C_SUCCESS; }