/** * Copyright (c) 2025 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "network/server/server.h" #include "assert/assert.h" #include "util/memory.h" #include "console/console.h" errorret_t networkedServerStart( server_t *server, const serverstart_t start ) { assertNotNull(server, "Server is null"); assertTrue(start.type == SERVER_TYPE_NETWORKED,"Invalid server type"); assertTrue(server->state == SERVER_STATE_STOPPED,"Server is already running"); assertIsMainThread("Server start must be on main thread"); // Initialize the networked server. consolePrint("Starting server on port %d...\n", start.networked.port); memoryZero(&server->networked, sizeof(server->networked)); // Initialize the server socket server->networked.socket = socket(AF_INET, SOCK_STREAM, 0); if(server->networked.socket < 0) { return error("Failed to create socket"); } server->networked.address.sin_family = AF_INET; server->networked.address.sin_addr.s_addr = INADDR_ANY; server->networked.address.sin_port = htons(start.networked.port); // Bind the socket to the address and port int32_t res = bind( server->networked.socket, (struct sockaddr *)&server->networked.address, sizeof(server->networked.address) ); if(res != 0) { close(server->networked.socket); return error("Failed to bind socket"); } // Set the socket to listen for incoming connections res = listen(server->networked.socket, SERVER_MAX_CLIENTS); if(res != 0) { close(server->networked.socket); return error("Failed to listen on socket"); } // Initialize mutex and condition variable pthread_mutex_init(&server->networked.lock, NULL); pthread_cond_init(&server->networked.cond, NULL); // Start the server thread. server->state = SERVER_STATE_STARTING; res = pthread_create(&server->thread, NULL, networkedServerThread, server); if(res != 0) { perror("Failed to create server thread"); serverStop(); return error("Failed to create server thread"); } // Wait for the thread to start. pthread_mutex_lock(&server->networked.lock); while(server->state == SERVER_STATE_STARTING) { pthread_cond_wait(&server->networked.cond, &server->networked.lock); } pthread_mutex_unlock(&server->networked.lock); // Server started, hand back. consolePrint("Server started."); return ERROR_OK; } void networkedServerStop(server_t *server) { assertNotNull(server, "Server is null"); assertTrue(server->state != SERVER_STATE_STOPPED, "Server is already stopped"); assertTrue(server->state != SERVER_STATE_STARTING, "Server is starting"); assertIsMainThread("Server stop must be on main thread"); // Notify thread to stop pthread_mutex_lock(&server->networked.lock); server->state = SERVER_STATE_STOPPING; pthread_cond_signal(&server->networked.cond); pthread_mutex_unlock(&server->networked.lock); // Disconnect clients uint8_t i = 0; do { serverclient_t *client = &server->clients[i++]; if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) continue; serverClientClose(client); } while(i < SERVER_MAX_CLIENTS); // Wait for the thread to stop int32_t maxWaits = 0; pthread_mutex_lock(&server->networked.lock); while(server->state == SERVER_STATE_STOPPING) { if(maxWaits++ >= 1000) { consolePrint("Server thread did not stop in time, forcing exit."); server->state = SERVER_STATE_STOPPED; break; } pthread_cond_wait(&server->networked.cond, &server->networked.lock); } pthread_mutex_unlock(&server->networked.lock); // Destroy mutex and condition variable pthread_mutex_destroy(&server->networked.lock); pthread_cond_destroy(&server->networked.cond); // Close the socket if(server->networked.socket >= 0) { close(server->networked.socket); server->networked.socket = -1; } // Term the thread. if(server->thread) { pthread_join(server->thread, NULL); server->thread = 0; } } void * networkedServerThread(void *arg) { assertNotNull(arg, "Server is null"); assertNotMainThread("Server thread must not be main thread"); server_t *server = (server_t *)arg; // Notify main thread that the server is running pthread_mutex_lock(&server->networked.lock); server->state = SERVER_STATE_RUNNING; pthread_cond_signal(&server->networked.cond); pthread_mutex_unlock(&server->networked.lock); struct timeval timeout; fd_set readfds; // Main thread loop. while(server->state == SERVER_STATE_RUNNING) { // Prepare the select call, used for waiting for incoming connections. FD_ZERO(&readfds); FD_SET(server->networked.socket, &readfds); timeout.tv_sec = 1; timeout.tv_usec = 0; // Wait for incoming connections int32_t activity = select( server->networked.socket + 1, &readfds, NULL, NULL, &timeout ); // Timeout or no activity if(activity <= 0) continue; // Check if there is a new connection if(!FD_ISSET(server->networked.socket, &readfds)) continue; // Accept new connection. int32_t clientSocket = accept(server->networked.socket, NULL, NULL); if(clientSocket < 0) { consolePrint("Failed to accept connection"); continue; } // Find an available client slot. serverclient_t *client = NULL; uint8_t i = 0; do { if(server->clients[i].state != SERVER_CLIENT_STATE_DISCONNECTED) { i++; continue; } client = &server->clients[i]; break; } while(i < SERVER_MAX_CLIENTS); // No available slots were found (Server full). This is the only time a // packet is sent without a client thread. if(!client) { packet_t packet; serverclient_t tempClient = { .networked = { .socket = clientSocket, }, .server = server, .state = SERVER_CLIENT_STATE_ACCEPTING }; packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_SERVER_FULL); networkedServerClientWritePacket(&tempClient, &packet); if(errorCheck()) errorPrint(); consolePrint("Client %i disconnected: Server full."); close(clientSocket); continue; } // Hand off to server client for acceptance. Only one client can be // accepted at a time at the moment. errorret_t ret = serverClientAccept(client, (serverclientaccept_t){ .server = server, .networked = { .socket = clientSocket, } }); if(ret != ERROR_OK) { consolePrint("Failed to accept client connection", errorString()); errorFlush(); close(clientSocket); } } // Thread loop was exited. This is only possible when the server is stopped. assertTrue( server->state != SERVER_STATE_RUNNING, "Server thread exiting while server is running?" ); // Notify main thread that the server has stopped pthread_mutex_lock(&server->networked.lock); server->state = SERVER_STATE_STOPPED; pthread_cond_signal(&server->networked.cond); pthread_mutex_unlock(&server->networked.lock); return NULL; }