Prepping for singleplayer style client and server.
This commit is contained in:
@ -7,6 +7,12 @@
|
|||||||
|
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
|
|
||||||
|
pthread_t assertMainThread = 0;
|
||||||
|
|
||||||
|
void assertInit() {
|
||||||
|
assertMainThread = pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
void assertTrueImpl(
|
void assertTrueImpl(
|
||||||
const char *file,
|
const char *file,
|
||||||
const int32_t line,
|
const int32_t line,
|
||||||
|
@ -8,6 +8,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
|
||||||
|
extern pthread_t assertMainThread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the assert system.
|
||||||
|
*/
|
||||||
|
void assertInit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert a given value to be true.
|
* Assert a given value to be true.
|
||||||
*
|
*
|
||||||
@ -128,4 +135,10 @@ void assertMemoryRangeMatchesImpl(
|
|||||||
#define assertStrLenMin(str, len, message) \
|
#define assertStrLenMin(str, len, message) \
|
||||||
assertTrue(strlen(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
|
// EOF
|
10
src/main.c
10
src/main.c
@ -8,6 +8,7 @@
|
|||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
#include "server/server.h"
|
#include "server/server.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
bool_t exitRequested = false;
|
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) {
|
if(ret != ERROR_OK) {
|
||||||
consolePrint("Failed to start server: %s", errorString());
|
consolePrint("Failed to start server: %s", errorString());
|
||||||
errorFlush();
|
errorFlush();
|
||||||
@ -37,6 +44,7 @@ void cmdClose(const consolecmdexec_t *exec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
assertInit();
|
||||||
consoleInit();
|
consoleInit();
|
||||||
serverInit();
|
serverInit();
|
||||||
|
|
||||||
|
@ -12,3 +12,5 @@ target_sources(${DUSK_TARGET_NAME}
|
|||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(packet)
|
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
|
PRIVATE
|
||||||
packet.c
|
packet.c
|
||||||
packetwelcome.c
|
packetwelcome.c
|
||||||
|
packetdisconnect.c
|
||||||
)
|
)
|
@ -14,6 +14,8 @@ typedef enum {
|
|||||||
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
|
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
|
||||||
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
|
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
|
||||||
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
|
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
|
||||||
|
PACKET_DISCONNECT_REASON_SERVER_FULL = 3,
|
||||||
|
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN = 4,
|
||||||
} packetdisconnectreason_t;
|
} packetdisconnectreason_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -17,142 +17,25 @@ void serverInit() {
|
|||||||
SERVER.state = SERVER_STATE_STOPPED;
|
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) {
|
if(SERVER.state != SERVER_STATE_STOPPED) {
|
||||||
return error("Server is already running");
|
return error("Server is already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
consolePrint("Starting server on port %d...\n", port);
|
SERVER.type = start.type;
|
||||||
memoryZero(&SERVER, sizeof(server_t));
|
|
||||||
|
|
||||||
// Initialize the server socket
|
// Hand off to relevant server type to start.
|
||||||
SERVER.serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
switch(start.type) {
|
||||||
if(SERVER.serverSocket < 0) {
|
case SERVER_TYPE_NETWORKED:
|
||||||
return error("Failed to create socket");
|
return networkedServerStart(&SERVER, start);
|
||||||
}
|
|
||||||
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];
|
|
||||||
break;
|
break;
|
||||||
} while(i < SERVER_MAX_CLIENTS);
|
|
||||||
|
|
||||||
// Can we receive this client?
|
default:
|
||||||
if(!client) {
|
assertUnreachable("Invalid server type");
|
||||||
const char_t *errMsg = "ERR|Server full";
|
|
||||||
send(clientSocket, errMsg, strlen(errMsg), 0);
|
|
||||||
consolePrint("Server full, closing connection.");
|
|
||||||
close(clientSocket);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
uint8_t serverGetClientCount() {
|
||||||
@ -165,46 +48,18 @@ uint8_t serverGetClientCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void serverStop() {
|
void serverStop() {
|
||||||
|
assertIsMainThread("Server stop must be on main thread");
|
||||||
if(SERVER.state == SERVER_STATE_STOPPED) return;
|
if(SERVER.state == SERVER_STATE_STOPPED) return;
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
SERVER.state != SERVER_STATE_STARTING,
|
|
||||||
"Stopping a server that is starting?\n"
|
|
||||||
);
|
|
||||||
|
|
||||||
consolePrint("Stopping server...");
|
consolePrint("Stopping server...");
|
||||||
SERVER.state = SERVER_STATE_STOPPING;
|
switch(SERVER.type) {
|
||||||
|
case SERVER_TYPE_NETWORKED:
|
||||||
|
networkedServerStop(&SERVER);
|
||||||
|
break;
|
||||||
|
|
||||||
// Wait for the server thread to finish
|
default:
|
||||||
int32_t maxWaits = 0;
|
assertUnreachable("Invalid server type");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.");
|
consolePrint("Server stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "serverclient.h"
|
#include "serverclient.h"
|
||||||
#include <netinet/in.h>
|
#include "server/networked/networkedserver.h"
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
#define SERVER_MAX_CLIENTS 32
|
#define SERVER_MAX_CLIENTS 1
|
||||||
#define SERVER_MAX_CHANNELS 2
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SERVER_STATE_STOPPED,
|
SERVER_STATE_STOPPED,
|
||||||
@ -20,12 +18,29 @@ typedef enum {
|
|||||||
SERVER_STATE_STOPPING
|
SERVER_STATE_STOPPING
|
||||||
} serverstate_t;
|
} 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;
|
serverstate_t state;
|
||||||
|
servertype_t type;
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
int serverSocket;
|
|
||||||
struct sockaddr_in serverAddress;
|
|
||||||
serverclient_t clients[SERVER_MAX_CLIENTS];
|
serverclient_t clients[SERVER_MAX_CLIENTS];
|
||||||
|
|
||||||
|
union {
|
||||||
|
networkedserver_t networked;
|
||||||
|
};
|
||||||
} server_t;
|
} server_t;
|
||||||
|
|
||||||
extern server_t SERVER;
|
extern server_t SERVER;
|
||||||
@ -47,17 +62,7 @@ void serverInit();
|
|||||||
* @param port The port number to bind the server to.
|
* @param port The port number to bind the server to.
|
||||||
* @return Returns an error code if the server fails to start.
|
* @return Returns an error code if the server fails to start.
|
||||||
*/
|
*/
|
||||||
errorret_t serverStart(uint16_t port);
|
errorret_t serverStart(const serverstart_t start);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the client count
|
* Get the client count
|
||||||
|
@ -5,206 +5,41 @@
|
|||||||
* https://opensource.org/licenses/MIT
|
* https://opensource.org/licenses/MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "serverclient.h"
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include <sys/socket.h>
|
|
||||||
#include "console/console.h"
|
#include "console/console.h"
|
||||||
|
|
||||||
errorret_t serverClientAccept(
|
errorret_t serverClientAccept(
|
||||||
serverclient_t *client,
|
serverclient_t *client,
|
||||||
const int32_t socket
|
const serverclientaccept_t accept
|
||||||
) {
|
) {
|
||||||
memoryZero(client, sizeof(serverclient_t));
|
memoryZero(client, sizeof(serverclient_t));
|
||||||
client->socket = socket;
|
assertNotNull(accept.server, "Server is NULL");
|
||||||
client->state = SERVER_CLIENT_STATE_ACCEPTING;
|
assertNotMainThread("Server client accept must not be main thread");
|
||||||
|
|
||||||
// Set timeout to 8 seconds
|
client->server = accept.server;
|
||||||
client->timeout.tv_sec = 8;
|
|
||||||
client->timeout.tv_usec = 0;
|
|
||||||
|
|
||||||
// Create a thread for the client
|
switch(accept.server->type) {
|
||||||
if(pthread_create(&client->thread, NULL, serverClientThread, client) != 0) {
|
case SERVER_TYPE_NETWORKED:
|
||||||
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
|
return networkedClientAccept(client, accept);
|
||||||
return error("Failed to create client thread");
|
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) {
|
void serverClientClose(serverclient_t *client) {
|
||||||
assertNotNull(client, "Client is NULL");
|
assertNotNull(client, "Client is NULL");
|
||||||
|
assertIsMainThread("Server client close must be on main thread");
|
||||||
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
|
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
|
||||||
|
|
||||||
// Mark client as disconnecting
|
switch(client->server->type) {
|
||||||
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
|
case SERVER_TYPE_NETWORKED:
|
||||||
|
networkedClientClose(client);
|
||||||
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");
|
|
||||||
break;
|
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
|
#pragma once
|
||||||
#include "error/error.h"
|
|
||||||
#include "server/packet/packet.h"
|
#include "server/packet/packet.h"
|
||||||
|
#include "server/networked/networkedclient.h"
|
||||||
|
|
||||||
|
typedef struct server_s server_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SERVER_CLIENT_STATE_DISCONNECTED,
|
SERVER_CLIENT_STATE_DISCONNECTED,
|
||||||
@ -16,75 +18,31 @@ typedef enum {
|
|||||||
SERVER_CLIENT_STATE_DISCONNECTING,
|
SERVER_CLIENT_STATE_DISCONNECTING,
|
||||||
} serverclientstate_t;
|
} serverclientstate_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct serverclientaccept_s {
|
||||||
int32_t socket;
|
server_t *server;
|
||||||
|
union {
|
||||||
|
networkedclientaccept_t networked;
|
||||||
|
};
|
||||||
|
} serverclientaccept_t;
|
||||||
|
|
||||||
|
typedef struct serverclient_s {
|
||||||
|
server_t *server;
|
||||||
serverclientstate_t state;
|
serverclientstate_t state;
|
||||||
pthread_t thread;
|
union {
|
||||||
struct timeval timeout;
|
networkedclient_t networked;
|
||||||
|
};
|
||||||
} serverclient_t;
|
} serverclient_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accepts an incoming connection from a server client.
|
* Accepts an incoming connection from a server client.
|
||||||
*
|
*
|
||||||
* @param client Pointer to the server client structure.
|
* @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.
|
* @return Error code indicating success or failure.
|
||||||
*/
|
*/
|
||||||
errorret_t serverClientAccept(
|
errorret_t serverClientAccept(
|
||||||
serverclient_t *client,
|
serverclient_t *client,
|
||||||
const int32_t socket
|
const serverclientaccept_t accept
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,23 +51,3 @@ ssize_t serverClientReceive(
|
|||||||
* @param client Pointer to the server client structure.
|
* @param client Pointer to the server client structure.
|
||||||
*/
|
*/
|
||||||
void serverClientClose(serverclient_t *client);
|
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