net changes?
This commit is contained in:
@ -9,5 +9,4 @@ target_sources(${DUSK_TARGET_NAME}
|
|||||||
client.c
|
client.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(networked)
|
|
@ -6,122 +6,91 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "console/console.h"
|
#include "assert/assert.h"
|
||||||
#include "network/server/server.h"
|
|
||||||
|
|
||||||
client_t CLIENT;
|
client_t CLIENT;
|
||||||
|
|
||||||
void cmdJoin(const consolecmdexec_t *exec) {
|
|
||||||
clientconnect_t connect;
|
|
||||||
|
|
||||||
if(exec->argc > 0) {
|
|
||||||
connect.type = CLIENT_TYPE_NETWORKED;
|
|
||||||
connect.networked.port = SERVER_DEFAULT_PORT;
|
|
||||||
} else {
|
|
||||||
connect.type = CLIENT_TYPE_SINGLE_PLAYER;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t ret = clientConnect(connect);
|
|
||||||
if(ret == ERROR_OK) {
|
|
||||||
consolePrint("Connected to server");
|
|
||||||
} else {
|
|
||||||
consolePrint("Failed to connect to server: %s", errorString());
|
|
||||||
}
|
|
||||||
errorFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmdLeave(const consolecmdexec_t *exec) {
|
|
||||||
clientDisconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clientInit() {
|
void clientInit() {
|
||||||
memoryZero(&CLIENT, sizeof(client_t));
|
memoryZero(&CLIENT, sizeof(client_t));
|
||||||
consoleRegCmd("join", cmdJoin);
|
|
||||||
consoleRegCmd("leave", cmdLeave);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorret_t clientConnect(const clientconnect_t connect) {
|
void clientConnect(const clientconnect_t connect) {
|
||||||
errorret_t ret;
|
int32_t error;
|
||||||
|
|
||||||
// Don't connect if already connected.
|
assertTrue(
|
||||||
if(CLIENT.state != CLIENT_STATE_DISCONNECTED) {
|
CLIENT.state == CLIENT_STATE_DISCONNECTED,
|
||||||
return error("Client is already connected");
|
"Client is not in a disconnected state."
|
||||||
}
|
);
|
||||||
|
|
||||||
// Init the client.
|
// Reset state
|
||||||
|
packetQueueInit(&CLIENT.queue);
|
||||||
|
CLIENT.error = CLIENT_ERROR_NO_ERROR;
|
||||||
|
CLIENT.state = CLIENT_STATE_CONNECTING;
|
||||||
CLIENT.type = connect.type;
|
CLIENT.type = connect.type;
|
||||||
packetQueueInit(&CLIENT.packetQueue);
|
|
||||||
|
// Init the properties based on the type of client.
|
||||||
// Change how we connect based on the type.
|
|
||||||
switch(connect.type) {
|
switch(connect.type) {
|
||||||
case CLIENT_TYPE_NETWORKED:
|
case CLIENT_TYPE_NETWORKED:
|
||||||
ret = networkedClientConnect(&CLIENT, connect.networked);
|
networkClientConnect(&CLIENT, connect);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLIENT_TYPE_SINGLE_PLAYER:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assertUnreachable("Invalid client type");
|
assertUnreachable("Invalid client connection type.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be connected now.
|
// Spawn the connect thread which will be responsible from here on out.
|
||||||
if(ret != ERROR_OK) CLIENT.state = CLIENT_STATE_DISCONNECTED;
|
error = pthread_create(
|
||||||
return ret;
|
&CLIENT.connectThread,
|
||||||
}
|
NULL,
|
||||||
|
(void *(*)(void *))clientThreadConnect,
|
||||||
void clientUpdate() {
|
&CLIENT
|
||||||
packet_t packet;
|
);
|
||||||
int32_t ret;
|
if(error != 0) {
|
||||||
errorret_t err;
|
CLIENT.state = CLIENT_STATE_DISCONNECTED;
|
||||||
|
CLIENT.error = CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED;
|
||||||
// Don't do anything if not connected
|
}
|
||||||
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Pop the packet off the queue.
|
|
||||||
ret = packetQueuePopIn(
|
|
||||||
&CLIENT.packetQueue,
|
|
||||||
&packet
|
|
||||||
);
|
|
||||||
|
|
||||||
// No more packets to process, just break out.
|
|
||||||
if(ret == 0) break;
|
|
||||||
|
|
||||||
// An error occured?
|
|
||||||
if(ret < 0) {
|
|
||||||
clientDisconnect();
|
|
||||||
consolePrint("Failed to pop packet");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the packet
|
|
||||||
err = packetClientProcess(&packet, &CLIENT);
|
|
||||||
if(err != ERROR_OK) {
|
|
||||||
clientDisconnect();
|
|
||||||
consolePrint("Failed to process packet %s", errorString());
|
|
||||||
errorFlush();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} while(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clientDisconnect() {
|
void clientDisconnect() {
|
||||||
// Don't disconnect if already disconnected.
|
|
||||||
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
|
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
|
||||||
|
if(CLIENT.state == CLIENT_STATE_DISCONNECTING) return;
|
||||||
|
}
|
||||||
|
|
||||||
// Disconnect the client.
|
void clientUpdate() {
|
||||||
switch(CLIENT.type) {
|
assertIsMainThread("Client update called from non-main thread.");
|
||||||
case CLIENT_TYPE_NETWORKED:
|
|
||||||
networkedClientDisconnect(&CLIENT);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
assertUnreachable("Invalid client type");
|
|
||||||
}
|
|
||||||
|
|
||||||
consolePrint("Client disconnected");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clientDispose() {
|
void clientDispose() {
|
||||||
clientDisconnect();
|
if(CLIENT.state != CLIENT_STATE_DISCONNECTED) {
|
||||||
|
clientDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void * clientThreadConnect(void *arg) {
|
||||||
|
assertNotNull(arg, "Client thread connect argument is null.");
|
||||||
|
assertNotMainThread("Client thread connect called from main thread.");
|
||||||
|
|
||||||
|
client_t *client = (client_t *)arg;
|
||||||
|
assertTrue(client == &CLIENT, "Invalid client thread arg.");
|
||||||
|
|
||||||
|
if(client->state) return NULL;
|
||||||
|
|
||||||
|
switch(client->type) {
|
||||||
|
case CLIENT_TYPE_NETWORKED:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CLIENT_TYPE_SINGLE_PLAYER:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertUnreachable("Invalid client type.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
}
|
}
|
@ -6,14 +6,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "network/client/networked/networkedclient.h"
|
|
||||||
#include "network/packet/packetqueue.h"
|
#include "network/packet/packetqueue.h"
|
||||||
|
#include "networkclient.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of connection used for a client.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CLIENT_TYPE_NETWORKED,
|
CLIENT_TYPE_SINGLE_PLAYER = 0,
|
||||||
CLIENT_TYPE_SINGLE_PLAYER,
|
CLIENT_TYPE_NETWORKED = 1,
|
||||||
} clienttype_t;
|
} clienttype_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of the client and what it is doing.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CLIENT_STATE_DISCONNECTED = 0,
|
CLIENT_STATE_DISCONNECTED = 0,
|
||||||
CLIENT_STATE_CONNECTING,
|
CLIENT_STATE_CONNECTING,
|
||||||
@ -21,50 +27,91 @@ typedef enum {
|
|||||||
CLIENT_STATE_DISCONNECTING,
|
CLIENT_STATE_DISCONNECTING,
|
||||||
} clientstate_t;
|
} clientstate_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information used to connect to a server.
|
||||||
|
*/
|
||||||
typedef struct clientconnect_s {
|
typedef struct clientconnect_s {
|
||||||
clienttype_t type;
|
clienttype_t type;
|
||||||
union {
|
|
||||||
networkedclientconnect_t networked;
|
|
||||||
};
|
|
||||||
} clientconnect_t;
|
} clientconnect_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error codes for the client.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CLIENT_ERROR_NO_ERROR = 0,
|
||||||
|
CLIENT_ERROR_CONNECT_THREAD_SPAWN_FAILED,
|
||||||
|
} clienterror_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The clients state and information.
|
||||||
|
*/
|
||||||
typedef struct client_s {
|
typedef struct client_s {
|
||||||
clientstate_t state;
|
clientstate_t state;
|
||||||
clienttype_t type;
|
clienttype_t type;
|
||||||
packetqueue_t packetQueue;
|
packetqueue_t queue;
|
||||||
|
clienterror_t error;
|
||||||
|
|
||||||
|
pthread_t connectThread;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
networkedclient_t networked;
|
networkclient_t network;
|
||||||
};
|
};
|
||||||
} client_t;
|
} client_t;
|
||||||
|
|
||||||
extern client_t CLIENT;
|
extern client_t CLIENT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the client.
|
* Initializes the client object, does not perform any connection.
|
||||||
*
|
|
||||||
* @return Error code indicating success or failure.
|
|
||||||
*/
|
*/
|
||||||
void clientInit();
|
void clientInit();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects to a server.
|
* Initiates a client connection, this function will not block and will return
|
||||||
|
* immediately even if a connection issue occurs. It is up to the caller to
|
||||||
|
* handle the connection state and any errors that occur.
|
||||||
*
|
*
|
||||||
* @param connect Connection information.
|
* Client must be already disconnected before calling this function.
|
||||||
* @return Error code indicating success or failure.
|
*
|
||||||
|
* @param connect The connection information to use.
|
||||||
*/
|
*/
|
||||||
errorret_t clientConnect(const clientconnect_t connect);
|
void clientConnect(const clientconnect_t connect);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the client state.
|
* Requests the client to disconnect from the server. This will not block and
|
||||||
*/
|
* will return immediately. The client will handle the disconnection in the
|
||||||
void clientUpdate();
|
* background.
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the client from the server.
|
|
||||||
*/
|
*/
|
||||||
void clientDisconnect();
|
void clientDisconnect();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up the client resources.
|
* Internal method that writes a packet to the client immediately.
|
||||||
*/
|
*/
|
||||||
void clientDispose();
|
clienterror_t clientWritePacket(const packet_t *packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal method that reads a packet from the client immediately.
|
||||||
|
*
|
||||||
|
* @param packet The packet to read into.
|
||||||
|
* @return Error code indicating success or failure.
|
||||||
|
*/
|
||||||
|
clienterror_t clientReadPacket(const packet_t *packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main update loop for the client, this will handle all incoming packets and
|
||||||
|
* update the client state.
|
||||||
|
*/
|
||||||
|
void clientUpdate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of the client, cleans up any resources and will force disconnect if
|
||||||
|
* not already disconnected.
|
||||||
|
*/
|
||||||
|
void clientDispose();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread function responsible for initiating and handshaking the connection.
|
||||||
|
*
|
||||||
|
* @param arg The client connection information.
|
||||||
|
* @return Returns NULL, unless an error occurs.
|
||||||
|
*/
|
||||||
|
void * clientThreadConnect(void *arg);
|
8
src/network/client/networkclient.c
Normal file
8
src/network/client/networkclient.c
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "networkclient.h"
|
24
src/network/client/networkclient.h
Normal file
24
src/network/client/networkclient.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct client_s client_t;
|
||||||
|
typedef struct clientconnect_s clientconnect_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *nothing;
|
||||||
|
} networkclient_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the client.
|
||||||
|
*
|
||||||
|
* @param client The client to initialize.
|
||||||
|
* @param connect The connection information.
|
||||||
|
*/
|
||||||
|
void networkClientConnect(client_t *client, const clientconnect_t connect);
|
@ -1,10 +0,0 @@
|
|||||||
# 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
|
|
||||||
)
|
|
@ -1,417 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2025 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "network/client/client.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "console/console.h"
|
|
||||||
|
|
||||||
errorret_t networkedClientConnect(
|
|
||||||
client_t *client,
|
|
||||||
const networkedclientconnect_t connInfo
|
|
||||||
) {
|
|
||||||
int32_t ret;
|
|
||||||
packet_t packet;
|
|
||||||
errorret_t err;
|
|
||||||
char_t *ip = "127.0.0.1";
|
|
||||||
|
|
||||||
// Validate the params
|
|
||||||
assertNotNull(client, "Client is NULL");
|
|
||||||
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
|
|
||||||
assertIsMainThread("Client connect must be on main thread");
|
|
||||||
|
|
||||||
// Set the state to connecting.
|
|
||||||
client->state = CLIENT_STATE_CONNECTING;
|
|
||||||
consolePrint("Connecting to server %s:%d", ip, connInfo.port);
|
|
||||||
|
|
||||||
// Create a socket
|
|
||||||
client->networked.socket = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if(client->networked.socket < 0) {
|
|
||||||
return error(
|
|
||||||
"Failed to create socket %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set ip address and port
|
|
||||||
client->networked.address.sin_family = AF_INET;
|
|
||||||
client->networked.address.sin_port = htons(connInfo.port);
|
|
||||||
client->networked.address.sin_addr.s_addr = inet_addr(ip);
|
|
||||||
ret = inet_pton(AF_INET, ip, &client->networked.address.sin_addr);
|
|
||||||
if(ret <= 0) {
|
|
||||||
close(client->networked.socket);
|
|
||||||
return error(
|
|
||||||
"Invalid or bad IP address %s: %s",
|
|
||||||
ip,
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the server
|
|
||||||
ret = connect(
|
|
||||||
client->networked.socket,
|
|
||||||
(struct sockaddr *)&client->networked.address,
|
|
||||||
sizeof(client->networked.address)
|
|
||||||
);
|
|
||||||
if(ret < 0) {
|
|
||||||
close(client->networked.socket);
|
|
||||||
switch(errno) {
|
|
||||||
case ECONNREFUSED:
|
|
||||||
return error("Failed to connect: Connection refused");
|
|
||||||
case ETIMEDOUT:
|
|
||||||
return error("Failed to connect: Connection timed out");
|
|
||||||
case ENETUNREACH:
|
|
||||||
return error("Failed to connect: Network unreachable");
|
|
||||||
default:
|
|
||||||
return error("Failed to connect: Unknown error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize mutexes and locks and condition variable
|
|
||||||
pthread_mutex_init(&client->networked.lock, NULL);
|
|
||||||
pthread_cond_init(&client->networked.cond, NULL);
|
|
||||||
pthread_mutex_init(&client->networked.readLock, NULL);
|
|
||||||
pthread_mutex_init(&client->networked.writeLock, NULL);
|
|
||||||
|
|
||||||
// Send the version
|
|
||||||
{
|
|
||||||
const char_t *message = "DUSK|"DUSK_VERSION;
|
|
||||||
ssize_t sent = send(
|
|
||||||
client->networked.socket,
|
|
||||||
message,
|
|
||||||
strlen(message),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should now receive a welcome packet or a disconnect packet.
|
|
||||||
err = networkedClientReadPacket(client, &packet);
|
|
||||||
if(err) return err;
|
|
||||||
switch(packet.type) {
|
|
||||||
case PACKET_TYPE_WELCOME:
|
|
||||||
err = packetWelcomeClientProcess(&packet, client);
|
|
||||||
if(err) return err;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PACKET_TYPE_DISCONNECT:
|
|
||||||
err = packetDisconnectClientProcess(&packet, client);
|
|
||||||
if(err) return err;
|
|
||||||
return error("Server disconnected");
|
|
||||||
|
|
||||||
default:
|
|
||||||
return error("Server did not send welcome message.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection was established, hand off to read thread
|
|
||||||
ret = pthread_create(
|
|
||||||
&client->networked.readThread,
|
|
||||||
NULL,
|
|
||||||
networkedClientReadThread,
|
|
||||||
client
|
|
||||||
);
|
|
||||||
if(ret != 0) {
|
|
||||||
close(client->networked.socket);
|
|
||||||
pthread_mutex_destroy(&client->networked.readLock);
|
|
||||||
pthread_mutex_destroy(&client->networked.writeLock);
|
|
||||||
return error(
|
|
||||||
"Failed to create client read thread %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the read thread to signal that it has started
|
|
||||||
pthread_mutex_lock(&client->networked.lock);
|
|
||||||
while (client->state == CLIENT_STATE_CONNECTING) {
|
|
||||||
pthread_cond_wait(&client->networked.cond, &client->networked.lock);
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&client->networked.lock);
|
|
||||||
|
|
||||||
// Start the write thread after the handshake
|
|
||||||
ret = pthread_create(
|
|
||||||
&client->networked.writeThread,
|
|
||||||
NULL,
|
|
||||||
networkedClientWriteThread, // Renamed from networkedClientRightThread
|
|
||||||
client
|
|
||||||
);
|
|
||||||
if(ret != 0) {
|
|
||||||
close(client->networked.socket);
|
|
||||||
pthread_mutex_destroy(&client->networked.readLock);
|
|
||||||
pthread_mutex_destroy(&client->networked.writeLock);
|
|
||||||
return error(
|
|
||||||
"Failed to create client write thread %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void networkedClientDisconnect(client_t *client) {
|
|
||||||
assertNotNull(client, "Client is NULL");
|
|
||||||
assertIsMainThread("Client disconnect must be on main thread");
|
|
||||||
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
|
|
||||||
assertTrue(client->state == CLIENT_STATE_CONNECTED, "Client not connected");
|
|
||||||
|
|
||||||
pthread_mutex_lock(&client->networked.lock);
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTING;
|
|
||||||
|
|
||||||
struct timespec timeout;
|
|
||||||
clock_gettime(CLOCK_REALTIME, &timeout);
|
|
||||||
timeout.tv_sec += 1; // Wait for up to 1 second
|
|
||||||
|
|
||||||
while (client->state == CLIENT_STATE_DISCONNECTING) {
|
|
||||||
if(pthread_cond_timedwait(
|
|
||||||
&client->networked.cond,
|
|
||||||
&client->networked.lock,
|
|
||||||
&timeout
|
|
||||||
) == ETIMEDOUT) {
|
|
||||||
consolePrint("Client disconnect timed out, force closing");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&client->networked.lock);
|
|
||||||
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTED;
|
|
||||||
|
|
||||||
// Wait for the threads to finish
|
|
||||||
if (client->networked.readThread) {
|
|
||||||
pthread_join(client->networked.readThread, NULL);
|
|
||||||
client->networked.readThread = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->networked.writeThread) {
|
|
||||||
pthread_join(client->networked.writeThread, NULL);
|
|
||||||
client->networked.writeThread = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy read and write locks
|
|
||||||
pthread_mutex_destroy(&client->networked.readLock);
|
|
||||||
pthread_mutex_destroy(&client->networked.writeLock);
|
|
||||||
|
|
||||||
// Close the socket
|
|
||||||
if (client->networked.socket) {
|
|
||||||
shutdown(client->networked.socket, SHUT_RDWR);
|
|
||||||
close(client->networked.socket);
|
|
||||||
client->networked.socket = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy mutex and condition variable
|
|
||||||
pthread_mutex_destroy(&client->networked.lock);
|
|
||||||
pthread_cond_destroy(&client->networked.cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networkedClientWrite(
|
|
||||||
const client_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");
|
|
||||||
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
|
|
||||||
|
|
||||||
if(client->state == 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(
|
|
||||||
const client_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networkedClientReadPacket(
|
|
||||||
const client_t *client,
|
|
||||||
packet_t *packet
|
|
||||||
) {
|
|
||||||
uint8_t buffer[sizeof(packet_t)];
|
|
||||||
|
|
||||||
assertNotNull(client, "Client is NULL");
|
|
||||||
assertNotNull(packet, "Packet is NULL");
|
|
||||||
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
|
|
||||||
|
|
||||||
if(client->state == CLIENT_STATE_DISCONNECTED) {
|
|
||||||
return error("Client is disconnected");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the packet header
|
|
||||||
ssize_t read = recv(
|
|
||||||
client->networked.socket,
|
|
||||||
buffer,
|
|
||||||
sizeof(packettype_t),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if(read != sizeof(packettype_t)) {
|
|
||||||
return error(
|
|
||||||
"Failed to read packet header %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
packet->type = *(packettype_t *)buffer;
|
|
||||||
|
|
||||||
// Read the packet length
|
|
||||||
read = recv(
|
|
||||||
client->networked.socket,
|
|
||||||
buffer,
|
|
||||||
sizeof(uint32_t),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if(read != sizeof(uint32_t)) {
|
|
||||||
return error(
|
|
||||||
"Failed to read packet length %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if(read > sizeof(packetdata_t)) {
|
|
||||||
return error("Packet length is too large");
|
|
||||||
}
|
|
||||||
packet->length = *(uint32_t *)buffer;
|
|
||||||
|
|
||||||
// Now, read the packet data
|
|
||||||
read = recv(
|
|
||||||
client->networked.socket,
|
|
||||||
(uint8_t *)&packet->data,
|
|
||||||
packet->length,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if(read != packet->length) {
|
|
||||||
return error(
|
|
||||||
"Failed to read packet data %s",
|
|
||||||
errno > 0 ? strerror(errno) : "Unknown error"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void * networkedClientReadThread(void *arg) {
|
|
||||||
assertNotNull(arg, "Client thread argument is NULL");
|
|
||||||
assertNotMainThread("Client thread must not be on main thread");
|
|
||||||
|
|
||||||
packet_t packet;
|
|
||||||
client_t *client = (client_t *)arg;
|
|
||||||
assertTrue(
|
|
||||||
client->type == CLIENT_TYPE_NETWORKED,
|
|
||||||
"Client thread argument is not networked"
|
|
||||||
);
|
|
||||||
assertTrue(
|
|
||||||
client->state == CLIENT_STATE_CONNECTING,
|
|
||||||
"Client thread argument is not connecting"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Notify the main thread that we are connected
|
|
||||||
pthread_mutex_lock(&client->networked.lock);
|
|
||||||
client->state = CLIENT_STATE_CONNECTED;
|
|
||||||
pthread_cond_signal(&client->networked.cond);
|
|
||||||
pthread_mutex_unlock(&client->networked.lock);
|
|
||||||
|
|
||||||
while(client->state == CLIENT_STATE_CONNECTED) {
|
|
||||||
pthread_mutex_lock(&client->networked.readLock);
|
|
||||||
|
|
||||||
// Read a packet from the server
|
|
||||||
errorret_t err = networkedClientReadPacket(client, &packet);
|
|
||||||
if(err) {
|
|
||||||
consolePrint("Failed to read packet %s", errorString());
|
|
||||||
errorFlush();
|
|
||||||
pthread_mutex_unlock(&client->networked.readLock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle disconnect packets here
|
|
||||||
if(packet.type == PACKET_TYPE_DISCONNECT) {
|
|
||||||
err = packetDisconnectClientProcess(&packet, client);
|
|
||||||
if(err) {
|
|
||||||
consolePrint(errorString());
|
|
||||||
errorFlush();
|
|
||||||
} else {
|
|
||||||
consolePrint("Server disconnected");
|
|
||||||
}
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTING;
|
|
||||||
pthread_mutex_unlock(&client->networked.readLock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
packetQueuePushIn(
|
|
||||||
&client->packetQueue,
|
|
||||||
&packet
|
|
||||||
);
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&client->networked.readLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTED;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&client->networked.lock);
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTED;
|
|
||||||
pthread_cond_signal(&client->networked.cond); // Notify the main thread
|
|
||||||
pthread_mutex_unlock(&client->networked.lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
void * networkedClientWriteThread(void *arg) {
|
|
||||||
packet_t packet;
|
|
||||||
int32_t ret;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
assertNotNull(arg, "Write thread argument is NULL");
|
|
||||||
assertNotMainThread("Write thread must not be on main thread");
|
|
||||||
|
|
||||||
client_t *client = (client_t *)arg;
|
|
||||||
assertTrue(
|
|
||||||
client->type == CLIENT_TYPE_NETWORKED,
|
|
||||||
"Write thread argument is not networked"
|
|
||||||
);
|
|
||||||
|
|
||||||
while(client->state == CLIENT_STATE_CONNECTED) {
|
|
||||||
pthread_mutex_lock(&client->networked.writeLock);
|
|
||||||
|
|
||||||
ret = packetQueuePopOut(
|
|
||||||
&client->packetQueue,
|
|
||||||
&packet
|
|
||||||
);
|
|
||||||
|
|
||||||
if(ret == 0) {
|
|
||||||
pthread_mutex_unlock(&client->networked.writeLock);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ret < 0) {
|
|
||||||
consolePrint("Failed to pop packet from queue %s", errorString());
|
|
||||||
errorFlush();
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTING;
|
|
||||||
pthread_mutex_unlock(&client->networked.writeLock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = networkedClientWritePacket(client, &packet);
|
|
||||||
if(err) {
|
|
||||||
consolePrint("Failed to write packet %s", errorString());
|
|
||||||
errorFlush();
|
|
||||||
client->state = CLIENT_STATE_DISCONNECTING;
|
|
||||||
pthread_mutex_unlock(&client->networked.writeLock);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&client->networked.writeLock); // Unlock after writing
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 <arpa/inet.h>
|
|
||||||
#include "network/packet/packetqueue.h"
|
|
||||||
|
|
||||||
typedef struct client_s client_t;
|
|
||||||
typedef struct clientconnect_s clientconnect_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint16_t port;
|
|
||||||
} networkedclientconnect_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t socket;
|
|
||||||
struct sockaddr_in address;
|
|
||||||
pthread_t readThread;
|
|
||||||
pthread_t writeThread;
|
|
||||||
pthread_mutex_t lock;
|
|
||||||
pthread_cond_t cond;
|
|
||||||
pthread_mutex_t readLock;
|
|
||||||
pthread_mutex_t writeLock;
|
|
||||||
} networkedclient_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to a networked server.
|
|
||||||
*
|
|
||||||
* @param client Pointer to the client structure.
|
|
||||||
* @param connect Connection information.
|
|
||||||
*/
|
|
||||||
errorret_t networkedClientConnect(
|
|
||||||
client_t *client,
|
|
||||||
const networkedclientconnect_t connect
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection to a networked server.
|
|
||||||
*
|
|
||||||
* @param client Pointer to the client structure.
|
|
||||||
*/
|
|
||||||
void networkedClientDisconnect(client_t *client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes data to the networked server.
|
|
||||||
*
|
|
||||||
* @param client Pointer to the client structure.
|
|
||||||
* @param data Data to write.
|
|
||||||
* @param len Length of the data.
|
|
||||||
* @return Error code.
|
|
||||||
*/
|
|
||||||
errorret_t networkedClientWrite(
|
|
||||||
const client_t *client,
|
|
||||||
const uint8_t *data,
|
|
||||||
const size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a packet to the networked server.
|
|
||||||
*
|
|
||||||
* @param client Pointer to the client structure.
|
|
||||||
* @param packet Pointer to the packet structure.
|
|
||||||
* @return Error code.
|
|
||||||
*/
|
|
||||||
errorret_t networkedClientWritePacket(
|
|
||||||
const client_t *client,
|
|
||||||
const packet_t *packet
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a packet from the networked server.
|
|
||||||
*
|
|
||||||
* @param client Pointer to the client structure.
|
|
||||||
* @param packet Pointer to the packet structure to read into.
|
|
||||||
* @return Error code.
|
|
||||||
*/
|
|
||||||
errorret_t networkedClientReadPacket(const client_t *client, packet_t *packet);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread function for handling networked client connections.
|
|
||||||
*
|
|
||||||
* @param arg Pointer to the client structure.
|
|
||||||
* @return NULL.
|
|
||||||
*/
|
|
||||||
void * networkedClientReadThread(void *arg); // Renamed from networkedClientThread
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread function for handling additional client operations.
|
|
||||||
*
|
|
||||||
* @param arg Pointer to the client structure.
|
|
||||||
* @return NULL.
|
|
||||||
*/
|
|
||||||
void * networkedClientWriteThread(void *arg); // Renamed from networkedClientRightThread
|
|
Reference in New Issue
Block a user