Prepping for singleplayer style client and server.
This commit is contained in:
@ -7,6 +7,12 @@
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
pthread_t assertMainThread = 0;
|
||||
|
||||
void assertInit() {
|
||||
assertMainThread = pthread_self();
|
||||
}
|
||||
|
||||
void assertTrueImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
|
@ -8,6 +8,13 @@
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
extern pthread_t assertMainThread;
|
||||
|
||||
/**
|
||||
* Initialises the assert system.
|
||||
*/
|
||||
void assertInit();
|
||||
|
||||
/**
|
||||
* Assert a given value to be true.
|
||||
*
|
||||
@ -128,4 +135,10 @@ void assertMemoryRangeMatchesImpl(
|
||||
#define assertStrLenMin(str, len, message) \
|
||||
assertTrue(strlen(str) >= len, message)
|
||||
|
||||
#define assertIsMainThread(message) \
|
||||
assertTrue(pthread_self() == assertMainThread, message)
|
||||
|
||||
#define assertNotMainThread(message) \
|
||||
assertFalse(pthread_self() == assertMainThread, message)
|
||||
|
||||
// EOF
|
10
src/main.c
10
src/main.c
@ -8,6 +8,7 @@
|
||||
#include "console/console.h"
|
||||
#include "server/server.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
bool_t exitRequested = false;
|
||||
|
||||
@ -24,7 +25,13 @@ void cmdServe(const consolecmdexec_t *exec) {
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t ret = serverStart(port);
|
||||
errorret_t ret = serverStart((serverstart_t){
|
||||
.type = SERVER_TYPE_NETWORKED,
|
||||
.networked = {
|
||||
.port = 3030
|
||||
}
|
||||
});
|
||||
|
||||
if(ret != ERROR_OK) {
|
||||
consolePrint("Failed to start server: %s", errorString());
|
||||
errorFlush();
|
||||
@ -37,6 +44,7 @@ void cmdClose(const consolecmdexec_t *exec) {
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assertInit();
|
||||
consoleInit();
|
||||
serverInit();
|
||||
|
||||
|
@ -12,3 +12,5 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(packet)
|
||||
add_subdirectory(networked)
|
||||
add_subdirectory(singleplayer)
|
11
src/server/networked/CMakeLists.txt
Normal file
11
src/server/networked/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
networkedclient.c
|
||||
networkedserver.c
|
||||
)
|
255
src/server/networked/networkedclient.c
Normal file
255
src/server/networked/networkedclient.c
Normal file
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "server/server.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
#include <sys/socket.h>
|
||||
|
||||
errorret_t networkedClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(accept.server, "Server is NULL");
|
||||
assertTrue(
|
||||
accept.server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_ACCEPTING;
|
||||
client->networked.socket = accept.networked.socket;
|
||||
|
||||
// Set timeout to 8 seconds
|
||||
client->networked.timeout.tv_sec = 8;
|
||||
client->networked.timeout.tv_usec = 0;
|
||||
|
||||
// Create a thread for the client
|
||||
int32_t ret = pthread_create(
|
||||
&client->networked.thread,
|
||||
NULL,
|
||||
networkedClientThread,
|
||||
client
|
||||
);
|
||||
if(ret != 0) {
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
return error("Failed to create client thread");
|
||||
}
|
||||
|
||||
// Set socket timeout
|
||||
if(setsockopt(
|
||||
client->networked.socket,
|
||||
SOL_SOCKET,
|
||||
SO_RCVTIMEO,
|
||||
&client->networked.timeout,
|
||||
sizeof(client->networked.timeout)
|
||||
) < 0) {
|
||||
networkedClientCloseOnThread(client, "Failed to set socket timeout");
|
||||
return error("Failed to set socket timeout");
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
void networkedClientClose(serverclient_t *client) {
|
||||
assertIsMainThread("Server client close must be on main thread.");
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
// Mark client as disconnecting
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
|
||||
int32_t maxWaits = 0;
|
||||
while(client->state == SERVER_CLIENT_STATE_DISCONNECTING) {
|
||||
// Wait for the thread to finish
|
||||
usleep(1000);
|
||||
maxWaits++;
|
||||
if(maxWaits > 10) break;
|
||||
}
|
||||
|
||||
pthread_join(client->networked.thread, NULL);
|
||||
client->networked.thread = 0;
|
||||
|
||||
// Close the socket
|
||||
if(client->networked.socket != -1) {
|
||||
close(client->networked.socket);
|
||||
client->networked.socket = -1;
|
||||
}
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
consolePrint("Client %d disconnected.", client->networked.socket);
|
||||
}
|
||||
|
||||
void networkedClientCloseOnThread(
|
||||
serverclient_t *client,
|
||||
const char_t *reason
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(reason, "Reason is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertNotMainThread("Client close must not be main thread");
|
||||
|
||||
// Terminate the socket
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
close(client->networked.socket);
|
||||
client->networked.socket = -1;
|
||||
client->networked.thread = 0;
|
||||
consolePrint("Client %d disconnected: %s", client->networked.socket, reason);
|
||||
|
||||
// Mark this client as disconnected so it can be used again.
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
ssize_t networkedClientRead(
|
||||
serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(buffer, "Buffer is NULL");
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertTrue(len > 0, "Buffer length is 0");
|
||||
|
||||
assertTrue(
|
||||
client->state == SERVER_CLIENT_STATE_CONNECTED ||
|
||||
client->state == SERVER_CLIENT_STATE_ACCEPTING ||
|
||||
client->state == SERVER_CLIENT_STATE_DISCONNECTING,
|
||||
"Client is not connected, accepting or disconnecting"
|
||||
);
|
||||
|
||||
return recv(client->networked.socket, buffer, len, 0);
|
||||
}
|
||||
|
||||
errorret_t networkedClientWrite(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(data, "Data is NULL");
|
||||
assertTrue(len > 0, "Data length is 0");
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) {
|
||||
return error("Client is disconnected");
|
||||
}
|
||||
ssize_t sent = send(client->networked.socket, data, len, 0);
|
||||
if(sent < 0) return error("Failed to send data");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
errorret_t networkedClientWritePacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
) {
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID");
|
||||
assertTrue(packet->length > 0, "Packet length is 0");
|
||||
assertTrue(
|
||||
packet->length <= sizeof(packetdata_t),
|
||||
"Packet length is too large (1)"
|
||||
);
|
||||
|
||||
size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length;
|
||||
assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)");
|
||||
return networkedClientWrite(client, (const uint8_t *)packet, fullSize);
|
||||
}
|
||||
|
||||
void * networkedClientThread(void *arg) {
|
||||
assertNotNull(arg, "Client is NULL");
|
||||
assertNotMainThread("Client thread must not be main thread");
|
||||
|
||||
serverclient_t *client = (serverclient_t *)arg;
|
||||
char_t buffer[1024];
|
||||
ssize_t read;
|
||||
errorret_t err;
|
||||
packet_t packet;
|
||||
|
||||
assertNotNull(client->server, "Server is NULL");
|
||||
assertTrue(
|
||||
client->server->type == SERVER_TYPE_NETWORKED,
|
||||
"Server is not networked"
|
||||
);
|
||||
assertTrue(
|
||||
client->state == SERVER_CLIENT_STATE_ACCEPTING,
|
||||
"Client is not accepting?"
|
||||
);
|
||||
|
||||
// First message from the client should always be "DUSK|VERSION" to match
|
||||
// the server version.
|
||||
{
|
||||
const char_t *expecting = "DUSK|"DUSK_VERSION;
|
||||
read = networkedClientRead(client, buffer, sizeof(buffer));
|
||||
if(read <= 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = networkedClientWritePacket(client, &packet);
|
||||
networkedClientCloseOnThread(client, "Failed to receive version");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[read] = '\0'; // Null-terminate the string
|
||||
if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = networkedClientWritePacket(client, &packet);
|
||||
networkedClientCloseOnThread(client, "Invalid version");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Send DUSK back!
|
||||
packetWelcomeCreate(&packet);
|
||||
err = networkedClientWritePacket(client, &packet);
|
||||
if(err != ERROR_OK) {
|
||||
networkedClientCloseOnThread(client, "Failed to send welcome message");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_CONNECTED;
|
||||
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
|
||||
read = networkedClientRead(client, buffer, sizeof(buffer));
|
||||
|
||||
if(read <= 0) {
|
||||
networkedClientCloseOnThread(client, "Failed to receive data");
|
||||
break;
|
||||
}
|
||||
|
||||
buffer[read] = '\0'; // Null-terminate the string
|
||||
consolePrint("Received: %s", buffer);
|
||||
|
||||
|
||||
if(SERVER.state != SERVER_STATE_RUNNING) {
|
||||
networkedClientCloseOnThread(client, "Server is shutting down");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
packetDisconnectCreate(
|
||||
&packet,
|
||||
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN
|
||||
);
|
||||
networkedClientWritePacket(client, &packet);
|
||||
if(errorCheck()) errorPrint();
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
return NULL;
|
||||
}
|
100
src/server/networked/networkedclient.h
Normal file
100
src/server/networked/networkedclient.h
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct serverclient_s serverclient_t;
|
||||
typedef struct serverclientaccept_s serverclientaccept_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t socket;
|
||||
} networkedclientaccept_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t socket;
|
||||
pthread_t thread;
|
||||
struct timeval timeout;
|
||||
} networkedclient_t;
|
||||
|
||||
/**
|
||||
* Accepts an incoming connection from a networked client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param accept Accept structure containing client information.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedClientAccept(
|
||||
serverclient_t *client,
|
||||
const serverclientaccept_t accept
|
||||
);
|
||||
|
||||
/**
|
||||
* Closes the connection to a networked client. Waits for the thread to finish.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
*/
|
||||
void networkedClientClose(serverclient_t *client);
|
||||
|
||||
/**
|
||||
* Closes the connection to a networked client on the thread.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param reason Reason for closing the connection.
|
||||
*/
|
||||
void networkedClientCloseOnThread(
|
||||
serverclient_t *client,
|
||||
const char_t *reason
|
||||
);
|
||||
|
||||
/**
|
||||
* Receives data from a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param buffer Pointer to the buffer to store received data.
|
||||
* @param len Max length of the buffer.
|
||||
* @return Number of bytes received. 0 or less indicates an error.
|
||||
*/
|
||||
ssize_t networkedClientRead(
|
||||
serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes data to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param data Pointer to the data to send.
|
||||
* @param len Length of the data to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedClientWrite(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Writes a packet to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param packet Pointer to the packet to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t networkedClientWritePacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
);
|
||||
|
||||
/**
|
||||
* Thread function for handling a networked client.
|
||||
*
|
||||
* @param arg Pointer to the server client structure.
|
||||
* @return NULL.
|
||||
*/
|
||||
void * networkedClientThread(void *arg);
|
217
src/server/networked/networkedserver.c
Normal file
217
src/server/networked/networkedserver.c
Normal file
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "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");
|
||||
}
|
||||
|
||||
// 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.
|
||||
while(server->state == SERVER_STATE_STARTING) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
// Tell thread we want it to stop
|
||||
server->state = SERVER_STATE_STOPPING;
|
||||
|
||||
// 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);
|
||||
|
||||
// Now we wait a short time for the thread to stop.
|
||||
int32_t maxWaits = 0;
|
||||
while(SERVER.state == SERVER_STATE_STOPPING) {
|
||||
usleep(1000);
|
||||
maxWaits++;
|
||||
if(maxWaits < 1000) continue;
|
||||
|
||||
consolePrint("Server thread did not stop in time, forcing exit.");
|
||||
SERVER.state = SERVER_STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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;
|
||||
struct timeval timeout;
|
||||
fd_set readfds;
|
||||
server->state = SERVER_STATE_RUNNING;
|
||||
|
||||
// Main thread loop.
|
||||
while(server->state == SERVER_STATE_RUNNING) {
|
||||
// Wait a tiny bit to avoid large CPU usage.
|
||||
usleep(1000);
|
||||
|
||||
// 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
|
||||
if(activity == 0) continue;
|
||||
|
||||
// Check for errors
|
||||
if(activity < 0) {
|
||||
consolePrint("Error in select");
|
||||
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);
|
||||
networkedClientWritePacket(&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 we are stopped.
|
||||
server->state = SERVER_STATE_STOPPED;
|
||||
return NULL;
|
||||
}
|
51
src/server/networked/networkedserver.h
Normal file
51
src/server/networked/networkedserver.h
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
struct sockaddr_in address;
|
||||
} networkedserver_t;
|
||||
|
||||
typedef struct server_s server_t;
|
||||
typedef struct serverstart_s serverstart_t;
|
||||
|
||||
/**
|
||||
* Initialize the networked server
|
||||
*
|
||||
* This function initializes the networked server by setting up the ENet library
|
||||
* and creating a server host.
|
||||
*
|
||||
* @param server Pointer to the server structure.
|
||||
* @param start Information about how to start the server.
|
||||
* @return Returns an error code if the server fails to start.
|
||||
*/
|
||||
errorret_t networkedServerStart(server_t *server, const serverstart_t start);
|
||||
|
||||
/**
|
||||
* Stop the networked server
|
||||
*
|
||||
* This function stops the networked server by shutting down the ENet library
|
||||
* and closing the server host.
|
||||
*
|
||||
* @param server Pointer to the server structure.
|
||||
*/
|
||||
void networkedServerStop(server_t *server);
|
||||
|
||||
/**
|
||||
* Server thread function
|
||||
*
|
||||
* This function runs in a separate thread and handles incoming connections,
|
||||
* messages, and disconnections.
|
||||
*
|
||||
* @param arg Pointer to the argument passed to the thread.
|
||||
*/
|
||||
void * networkedServerThread(void *arg);
|
@ -8,4 +8,5 @@ target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
packet.c
|
||||
packetwelcome.c
|
||||
packetdisconnect.c
|
||||
)
|
@ -14,6 +14,8 @@ typedef enum {
|
||||
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
|
||||
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
|
||||
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
|
||||
PACKET_DISCONNECT_REASON_SERVER_FULL = 3,
|
||||
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN = 4,
|
||||
} packetdisconnectreason_t;
|
||||
|
||||
typedef struct {
|
||||
|
@ -17,142 +17,25 @@ void serverInit() {
|
||||
SERVER.state = SERVER_STATE_STOPPED;
|
||||
}
|
||||
|
||||
errorret_t serverStart(uint16_t port) {
|
||||
errorret_t serverStart(const serverstart_t start) {
|
||||
assertIsMainThread("Server start must be on main thread");
|
||||
|
||||
// Do not start a running server.
|
||||
if(SERVER.state != SERVER_STATE_STOPPED) {
|
||||
return error("Server is already running");
|
||||
}
|
||||
|
||||
consolePrint("Starting server on port %d...\n", port);
|
||||
memoryZero(&SERVER, sizeof(server_t));
|
||||
SERVER.type = start.type;
|
||||
|
||||
// Initialize the server socket
|
||||
SERVER.serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(SERVER.serverSocket < 0) {
|
||||
return error("Failed to create socket");
|
||||
}
|
||||
SERVER.serverAddress.sin_family = AF_INET;
|
||||
SERVER.serverAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
SERVER.serverAddress.sin_port = htons(port);
|
||||
|
||||
// Bind the socket to the address and port
|
||||
int32_t res = bind(
|
||||
SERVER.serverSocket,
|
||||
(struct sockaddr *)&SERVER.serverAddress,
|
||||
sizeof(SERVER.serverAddress)
|
||||
);
|
||||
if(res != 0) {
|
||||
close(SERVER.serverSocket);
|
||||
return error("Failed to bind socket");
|
||||
}
|
||||
|
||||
// Set the socket to listen for incoming connections
|
||||
res = listen(SERVER.serverSocket, SERVER_MAX_CLIENTS);
|
||||
if(res != 0) {
|
||||
close(SERVER.serverSocket);
|
||||
return error("Failed to listen on socket");
|
||||
}
|
||||
|
||||
// Start the server thread.
|
||||
SERVER.state = SERVER_STATE_STARTING;
|
||||
res = pthread_create(&SERVER.thread, NULL, serverThread, NULL);
|
||||
if(res != 0) {
|
||||
perror("Failed to create server thread");
|
||||
serverStop();
|
||||
return error("Failed to create server thread");
|
||||
}
|
||||
|
||||
// Wait
|
||||
while(SERVER.state == SERVER_STATE_STARTING) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
consolePrint("Server started.");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
void * serverThread(void *arg) {
|
||||
struct timeval timeout;
|
||||
fd_set readfds;
|
||||
|
||||
SERVER.state = SERVER_STATE_RUNNING;
|
||||
|
||||
while(SERVER.state == SERVER_STATE_RUNNING) {
|
||||
usleep(1000);
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(SERVER.serverSocket, &readfds);
|
||||
|
||||
// Fix timeout struct
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
// Wait for
|
||||
int32_t activity = select(
|
||||
SERVER.serverSocket + 1,
|
||||
&readfds,
|
||||
NULL,
|
||||
NULL,
|
||||
&timeout
|
||||
);
|
||||
|
||||
// Check for errors
|
||||
if(activity < 0) {
|
||||
consolePrint("Error in select");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Timeout
|
||||
if(activity == 0) continue;
|
||||
|
||||
// Check if there is a new connection
|
||||
if(!FD_ISSET(SERVER.serverSocket, &readfds)) continue;
|
||||
|
||||
// Accept new connection
|
||||
int32_t clientSocket = accept(SERVER.serverSocket, NULL, NULL);
|
||||
if(clientSocket < 0) {
|
||||
consolePrint("Failed to accept connection");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialize the client
|
||||
serverclient_t *client = NULL;
|
||||
uint8_t i = 0;
|
||||
do {
|
||||
if(SERVER.clients[i].state != SERVER_CLIENT_STATE_DISCONNECTED) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
client = &SERVER.clients[i];
|
||||
// Hand off to relevant server type to start.
|
||||
switch(start.type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
return networkedServerStart(&SERVER, start);
|
||||
break;
|
||||
} while(i < SERVER_MAX_CLIENTS);
|
||||
|
||||
// Can we receive this client?
|
||||
if(!client) {
|
||||
const char_t *errMsg = "ERR|Server full";
|
||||
send(clientSocket, errMsg, strlen(errMsg), 0);
|
||||
consolePrint("Server full, closing connection.");
|
||||
close(clientSocket);
|
||||
continue;
|
||||
default:
|
||||
assertUnreachable("Invalid server type");
|
||||
}
|
||||
|
||||
errorret_t ret = serverClientAccept(client, clientSocket);
|
||||
if(ret != ERROR_OK) {
|
||||
consolePrint("Failed to accept client connection", errorString());
|
||||
errorFlush();
|
||||
close(clientSocket);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Server thread exiting.\n");
|
||||
assertTrue(
|
||||
SERVER.state != SERVER_STATE_RUNNING,
|
||||
"Server thread exiting while server is running?"
|
||||
);
|
||||
|
||||
SERVER.thread = 0;
|
||||
SERVER.state = SERVER_STATE_STOPPED;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t serverGetClientCount() {
|
||||
@ -165,46 +48,18 @@ uint8_t serverGetClientCount() {
|
||||
}
|
||||
|
||||
void serverStop() {
|
||||
assertIsMainThread("Server stop must be on main thread");
|
||||
if(SERVER.state == SERVER_STATE_STOPPED) return;
|
||||
|
||||
assertTrue(
|
||||
SERVER.state != SERVER_STATE_STARTING,
|
||||
"Stopping a server that is starting?\n"
|
||||
);
|
||||
|
||||
consolePrint("Stopping server...");
|
||||
SERVER.state = SERVER_STATE_STOPPING;
|
||||
|
||||
// Wait for the server thread to finish
|
||||
int32_t maxWaits = 0;
|
||||
while(SERVER.state == SERVER_STATE_STOPPING) {
|
||||
usleep(1000);
|
||||
maxWaits++;
|
||||
if(maxWaits < 1000) continue;
|
||||
|
||||
consolePrint("Server thread did not stop in time, forcing exit.");
|
||||
SERVER.state = SERVER_STATE_STOPPED;
|
||||
switch(SERVER.type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
networkedServerStop(&SERVER);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid server type");
|
||||
}
|
||||
|
||||
// Kill the clients
|
||||
uint8_t i = 0;
|
||||
do {
|
||||
serverclient_t *client = &SERVER.clients[i++];
|
||||
serverClientClose(client);
|
||||
} while(i < SERVER_MAX_CLIENTS);
|
||||
|
||||
// Stop the server
|
||||
if(SERVER.serverSocket >= 0) {
|
||||
close(SERVER.serverSocket);
|
||||
SERVER.serverSocket = -1;
|
||||
}
|
||||
|
||||
if(SERVER.thread) {
|
||||
pthread_join(SERVER.thread, NULL);
|
||||
SERVER.thread = 0;
|
||||
}
|
||||
|
||||
consolePrint("Server stopped.");
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,9 @@
|
||||
|
||||
#pragma once
|
||||
#include "serverclient.h"
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "server/networked/networkedserver.h"
|
||||
|
||||
#define SERVER_MAX_CLIENTS 32
|
||||
#define SERVER_MAX_CHANNELS 2
|
||||
#define SERVER_MAX_CLIENTS 1
|
||||
|
||||
typedef enum {
|
||||
SERVER_STATE_STOPPED,
|
||||
@ -20,12 +18,29 @@ typedef enum {
|
||||
SERVER_STATE_STOPPING
|
||||
} serverstate_t;
|
||||
|
||||
typedef struct {
|
||||
typedef enum {
|
||||
SERVER_TYPE_SINGLE_PLAYER,
|
||||
SERVER_TYPE_NETWORKED
|
||||
} servertype_t;
|
||||
|
||||
typedef struct serverstart_s {
|
||||
servertype_t type;
|
||||
union {
|
||||
struct {
|
||||
uint16_t port;
|
||||
} networked;
|
||||
};
|
||||
} serverstart_t;
|
||||
|
||||
typedef struct server_s {
|
||||
serverstate_t state;
|
||||
servertype_t type;
|
||||
pthread_t thread;
|
||||
int serverSocket;
|
||||
struct sockaddr_in serverAddress;
|
||||
serverclient_t clients[SERVER_MAX_CLIENTS];
|
||||
|
||||
union {
|
||||
networkedserver_t networked;
|
||||
};
|
||||
} server_t;
|
||||
|
||||
extern server_t SERVER;
|
||||
@ -47,17 +62,7 @@ void serverInit();
|
||||
* @param port The port number to bind the server to.
|
||||
* @return Returns an error code if the server fails to start.
|
||||
*/
|
||||
errorret_t serverStart(uint16_t port);
|
||||
|
||||
/**
|
||||
* Server thread function
|
||||
*
|
||||
* This function runs in a separate thread and handles incoming connections,
|
||||
* messages, and disconnections.
|
||||
*
|
||||
* @param arg Pointer to the argument passed to the thread.
|
||||
*/
|
||||
void * serverThread(void *arg);
|
||||
errorret_t serverStart(const serverstart_t start);
|
||||
|
||||
/**
|
||||
* Get the client count
|
||||
|
@ -5,206 +5,41 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "serverclient.h"
|
||||
#include "server.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include <sys/socket.h>
|
||||
#include "console/console.h"
|
||||
|
||||
errorret_t serverClientAccept(
|
||||
serverclient_t *client,
|
||||
const int32_t socket
|
||||
const serverclientaccept_t accept
|
||||
) {
|
||||
memoryZero(client, sizeof(serverclient_t));
|
||||
client->socket = socket;
|
||||
client->state = SERVER_CLIENT_STATE_ACCEPTING;
|
||||
assertNotNull(accept.server, "Server is NULL");
|
||||
assertNotMainThread("Server client accept must not be main thread");
|
||||
|
||||
// Set timeout to 8 seconds
|
||||
client->timeout.tv_sec = 8;
|
||||
client->timeout.tv_usec = 0;
|
||||
client->server = accept.server;
|
||||
|
||||
// Create a thread for the client
|
||||
if(pthread_create(&client->thread, NULL, serverClientThread, client) != 0) {
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
return error("Failed to create client thread");
|
||||
switch(accept.server->type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
return networkedClientAccept(client, accept);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown server type");
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t serverClientSend(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(data, "Data is NULL");
|
||||
assertTrue(len > 0, "Data length is 0");
|
||||
|
||||
if(client->state != SERVER_CLIENT_STATE_ACCEPTING) {
|
||||
return error("Client is not accepting");
|
||||
}
|
||||
ssize_t sent = send(client->socket, data, len, 0);
|
||||
if(sent < 0) return error("Failed to send data");
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
errorret_t serverClientSendString(
|
||||
serverclient_t * client,
|
||||
const char_t *data
|
||||
) {
|
||||
return serverClientSend(client, (const uint8_t *)data, strlen(data));
|
||||
}
|
||||
|
||||
errorret_t serverClientSendPacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(packet, "Packet is NULL");
|
||||
assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID");
|
||||
assertTrue(packet->length > 0, "Packet length is 0");
|
||||
assertTrue(
|
||||
packet->length <= sizeof(packetdata_t),
|
||||
"Packet length is too large (1)"
|
||||
);
|
||||
|
||||
size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length;
|
||||
assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)");
|
||||
|
||||
return serverClientSend(client, (const uint8_t *)packet, fullSize);
|
||||
}
|
||||
|
||||
ssize_t serverClientReceive(
|
||||
serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(buffer, "Buffer is NULL");
|
||||
assertTrue(len > 0, "Buffer length is 0");
|
||||
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return -1;
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTING) return -1;
|
||||
|
||||
return recv(client->socket, buffer, len, 0);
|
||||
}
|
||||
|
||||
void serverClientClose(serverclient_t *client) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertIsMainThread("Server client close must be on main thread");
|
||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
|
||||
|
||||
// Mark client as disconnecting
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
|
||||
int32_t maxWaits = 0;
|
||||
while(client->state == SERVER_CLIENT_STATE_DISCONNECTING) {
|
||||
// Wait for the thread to finish
|
||||
usleep(1000);
|
||||
maxWaits++;
|
||||
if(maxWaits > 10) break;
|
||||
}
|
||||
|
||||
pthread_join(client->thread, NULL);
|
||||
client->thread = 0;
|
||||
|
||||
// Close the socket
|
||||
if(client->socket != -1) {
|
||||
close(client->socket);
|
||||
client->socket = -1;
|
||||
}
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
consolePrint("Client %d disconnected.", client->socket);
|
||||
}
|
||||
|
||||
void serverClientCloseOnThread(serverclient_t *client, const char_t *reason) {
|
||||
assertNotNull(client, "Client is NULL");
|
||||
assertNotNull(reason, "Reason is NULL");
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
||||
|
||||
close(client->socket);
|
||||
client->socket = -1;
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
client->thread = 0;
|
||||
|
||||
consolePrint("Client %d disconnected: %s", client->socket, reason);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void *serverClientThread(void *arg) {
|
||||
assertNotNull(arg, "Client is NULL");
|
||||
|
||||
serverclient_t *client = (serverclient_t *)arg;
|
||||
char_t buffer[1024];
|
||||
ssize_t read;
|
||||
errorret_t err;
|
||||
packet_t packet;
|
||||
|
||||
consolePrint("Accepting client on socket %d.", client->socket);
|
||||
|
||||
// Set socket timeout
|
||||
if(setsockopt(
|
||||
client->socket,
|
||||
SOL_SOCKET,
|
||||
SO_RCVTIMEO,
|
||||
&client->timeout,
|
||||
sizeof(client->timeout)
|
||||
) < 0) {
|
||||
serverClientCloseOnThread(client, "Failed to set socket timeout");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// First message from the client should always be "DUSK|VERSION" to match
|
||||
// the server version.
|
||||
{
|
||||
const char_t *expecting = "DUSK|"DUSK_VERSION;
|
||||
read = serverClientReceive(client, buffer, sizeof(buffer));
|
||||
if(read <= 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = serverClientSendPacket(client, &packet);
|
||||
serverClientCloseOnThread(client, "Failed to receive version");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buffer[read] = '\0'; // Null-terminate the string
|
||||
if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
|
||||
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
|
||||
err = serverClientSendPacket(client, &packet);
|
||||
serverClientCloseOnThread(client, "Invalid version");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Send DUSK back!
|
||||
packetWelcomeCreate(&packet);
|
||||
err = serverClientSendPacket(client, &packet);
|
||||
if(err != ERROR_OK) {
|
||||
serverClientCloseOnThread(client, "Failed to send welcome message");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client->state = SERVER_CLIENT_STATE_CONNECTED;
|
||||
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
|
||||
read = serverClientReceive(client, buffer, sizeof(buffer));
|
||||
|
||||
if(read <= 0) {
|
||||
serverClientCloseOnThread(client, "Failed to receive data");
|
||||
switch(client->server->type) {
|
||||
case SERVER_TYPE_NETWORKED:
|
||||
networkedClientClose(client);
|
||||
break;
|
||||
default:
|
||||
assertUnreachable("Unknown server type");
|
||||
}
|
||||
|
||||
buffer[read] = '\0'; // Null-terminate the string
|
||||
consolePrint("Received: %s", buffer);
|
||||
|
||||
|
||||
if(SERVER.state != SERVER_STATE_RUNNING) {
|
||||
serverClientCloseOnThread(client, "Server is shutting down");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
serverClientSendString(client, "Server is shutting down");
|
||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
||||
return NULL;
|
||||
}
|
@ -6,8 +6,10 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "server/packet/packet.h"
|
||||
#include "server/networked/networkedclient.h"
|
||||
|
||||
typedef struct server_s server_t;
|
||||
|
||||
typedef enum {
|
||||
SERVER_CLIENT_STATE_DISCONNECTED,
|
||||
@ -16,75 +18,31 @@ typedef enum {
|
||||
SERVER_CLIENT_STATE_DISCONNECTING,
|
||||
} serverclientstate_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t socket;
|
||||
typedef struct serverclientaccept_s {
|
||||
server_t *server;
|
||||
union {
|
||||
networkedclientaccept_t networked;
|
||||
};
|
||||
} serverclientaccept_t;
|
||||
|
||||
typedef struct serverclient_s {
|
||||
server_t *server;
|
||||
serverclientstate_t state;
|
||||
pthread_t thread;
|
||||
struct timeval timeout;
|
||||
union {
|
||||
networkedclient_t networked;
|
||||
};
|
||||
} serverclient_t;
|
||||
|
||||
/**
|
||||
* Accepts an incoming connection from a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param socket The socket file descriptor for the client.
|
||||
* @param accept Accept structure containing client information.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t serverClientAccept(
|
||||
serverclient_t *client,
|
||||
const int32_t socket
|
||||
);
|
||||
|
||||
/**
|
||||
* Sends data to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param data Pointer to the data to send.
|
||||
* @param len Length of the data to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t serverClientSend(
|
||||
serverclient_t * client,
|
||||
const uint8_t *data,
|
||||
const size_t len
|
||||
);
|
||||
|
||||
/**
|
||||
* Sends a string to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param data Pointer to the string to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t serverClientSendString(
|
||||
serverclient_t * client,
|
||||
const char_t *data
|
||||
);
|
||||
|
||||
/**
|
||||
* Sends a packet to a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param packet Pointer to the packet to send.
|
||||
* @return Error code indicating success or failure.
|
||||
*/
|
||||
errorret_t serverClientSendPacket(
|
||||
serverclient_t * client,
|
||||
const packet_t *packet
|
||||
);
|
||||
|
||||
/**
|
||||
* Receives data from a server client.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param buffer Pointer to the buffer to store received data.
|
||||
* @param len Max length of the buffer.
|
||||
* @return Number of bytes received. 0 or less indicates an error.
|
||||
*/
|
||||
ssize_t serverClientReceive(
|
||||
serverclient_t * client,
|
||||
uint8_t *buffer,
|
||||
const size_t len
|
||||
const serverclientaccept_t accept
|
||||
);
|
||||
|
||||
/**
|
||||
@ -93,23 +51,3 @@ ssize_t serverClientReceive(
|
||||
* @param client Pointer to the server client structure.
|
||||
*/
|
||||
void serverClientClose(serverclient_t *client);
|
||||
|
||||
/**
|
||||
* Closes the connection to a server client without waiting for the thread to
|
||||
* finish.
|
||||
*
|
||||
* @param client Pointer to the server client structure.
|
||||
* @param reason Reason for closing the connection.
|
||||
*/
|
||||
void serverClientCloseOnThread(
|
||||
serverclient_t *client,
|
||||
const char_t *reason
|
||||
);
|
||||
|
||||
/**
|
||||
* Thread function for handling a server client.
|
||||
*
|
||||
* @param arg Pointer to the server client structure.
|
||||
* @return NULL.
|
||||
*/
|
||||
void *serverClientThread(void *arg);
|
9
src/server/singleplayer/CMakeLists.txt
Normal file
9
src/server/singleplayer/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
)
|
Reference in New Issue
Block a user