232 lines
7.0 KiB
C
232 lines
7.0 KiB
C
/**
|
|
* 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;
|
|
} |