From 4ddb80b8b26f13b240a790d62e9047dfd9cd15f0 Mon Sep 17 00:00:00 2001 From: Barry Date: Thu, 7 Apr 2022 01:38:36 +0100 Subject: [PATCH] Basic message queuing implemented - Messages are now queued on reception by the server. - Message queue datastructures are now added. --- Makefile | 2 +- src/SilverMUD.org | 4 +- src/SilverMUDClient.c | 3 +- src/SilverMUDServer.c | 56 ++++++-- src/misc/constants.h | 9 ++ src/misc/inputoutput.c | 263 ++++++++++++++++++++++++++++++++++- src/misc/inputoutput.h | 95 ++++++++++++- src/misc/playerdata.h | 5 +- src/tests/outputQueue-test.c | 35 +++++ 9 files changed, 438 insertions(+), 34 deletions(-) create mode 100644 src/misc/constants.h create mode 100644 src/tests/outputQueue-test.c diff --git a/Makefile b/Makefile index 10dea79..a945a26 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ serversrc = $(wildcard src/misc/*.c) \ src/SilverMUDServer.c serverobj = $(serversrc:.c=.o) CLIENTLDFLAGS= -lpthread -lncurses -lgnutls -SERVERLDFLAGS= -lncurses -lgnutls +SERVERLDFLAGS= -lpthread -lncurses -lgnutls SilverMUDClient: $(clientobj) gcc -s -O3 -o $@ $^ $(CLIENTLDFLAGS) diff --git a/src/SilverMUD.org b/src/SilverMUD.org index a68d25d..30aa5c8 100644 --- a/src/SilverMUD.org +++ b/src/SilverMUD.org @@ -1,6 +1,6 @@ -* SilverMUD: An extensible terminal-top role playing game engine +* SilverMUD: The Hackable Terminal-Top Roleplaying Game. SilverMUD is a tool for creating engaging and communal stories, all over the -world through the internet. It's designed to give a game master the same power +world through the internet. It's designed to give a gamemaster the same power to improvise that they have at the table, through simple programming and easy-to-understand structures. ** Player's Guide diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c index 9fd91f5..409f765 100644 --- a/src/SilverMUDClient.c +++ b/src/SilverMUDClient.c @@ -13,11 +13,10 @@ #include #include #include +#include "misc/constants.h" #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputoutput.h" -#include "misc/inputhandling.h" -static int MAX = 2048; // A struct for passing arguments to our threads containing a file descriptor and a window pointer: typedef struct threadparameters diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c index 5725eb7..6061482 100644 --- a/src/SilverMUDServer.c +++ b/src/SilverMUDServer.c @@ -14,31 +14,32 @@ #include #include #include "misc/lists.h" +#include "misc/constants.h" #include "misc/playerdata.h" #include "misc/texteffects.h" #include "misc/inputoutput.h" -#include "misc/inputhandling.h" -const int PORT = 5000; -const int PLAYERCOUNT = 64; typedef struct sockaddr sockaddr; int main() { bool keepRunning = true; int socketFileDesc, connectionFileDesc, length, clientsAmount, - socketCheck, activityCheck, readLength, returnVal; + socketCheck, activityCheck, returnVal; fd_set connectedClients; int clientSockets[PLAYERCOUNT]; userMessage sendBuffer, receiveBuffer; playerInfo connectedPlayers[PLAYERCOUNT]; char testString[32] = "Hehe."; struct sockaddr_in serverAddress, clientAddress; + inputMessageQueue * inputQueue = createInputMessageQueue(); + outputMessageQueue * outputQueue = createOutputMessageQueue(); // Initialize playerdata: for (int index = 0; index < PLAYERCOUNT; index++) { - strcpy(connectedPlayers[index].playerName, "UNNAMED"); + sprintf(testString, "UNNAMED %d", index); + strcpy(connectedPlayers[index].playerName, testString); } // Give an intro: Display the Silverkin Industries logo and splash text. @@ -187,18 +188,47 @@ int main() if(FD_ISSET(socketCheck, &connectedClients)) { - messageReceive(tlssessions[index], &receiveBuffer); - sprintf(testString, "User %d", index); - strcpy(sendBuffer.senderName, testString); - userInputSanatize(receiveBuffer.messageContent, sizeof(receiveBuffer.messageContent)); - strcpy(sendBuffer.messageContent, receiveBuffer.messageContent); - for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) + if(messageReceive(tlssessions[index], &receiveBuffer) == -10) { - messageSend(tlssessions[sendIndex], &sendBuffer); + gnutls_bye(tlssessions[index], GNUTLS_SHUT_RDWR); + gnutls_deinit(tlssessions[index]); + shutdown(clientSockets[index], 2); + close(clientSockets[index]); + clientSockets[index] = 0; + tlssessions[index] = NULL; + gnutls_init(&tlssessions[index], GNUTLS_SERVER); + gnutls_priority_set_direct(tlssessions[index], "NORMAL:+ANON-ECDH:+ANON-DH", NULL); + gnutls_credentials_set(tlssessions[index], GNUTLS_CRD_ANON, &serverkey); + gnutls_handshake_set_timeout(tlssessions[index], GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + } + else + { + queueInputMessage(inputQueue, receiveBuffer, &connectedPlayers[index]); } } } - } + } + // TEMPORARY: MOVE INPUT MESSAGES TO OUTPUT MESSAGES: + while(inputQueue->currentLength > 0) + { + inputMessage * message = peekInputMessage(inputQueue); + strncpy(message->content->senderName, message->sender->playerName, 32); + userInputSanatize(message->content->messageContent, MAX); + if(message->content->messageContent[0] != '\n') + { + queueOutputMessage(outputQueue, *message->content); + } + dequeueInputMessage(inputQueue); + } + while(outputQueue->currentLength > 0) + { + outputMessage * message = peekOutputMessage(outputQueue); + for (int index = 0; index < PLAYERCOUNT; index++) + { + messageSend(tlssessions[index], message->content); + } + dequeueOutputMessage(outputQueue); + } } return 0; } diff --git a/src/misc/constants.h b/src/misc/constants.h new file mode 100644 index 0000000..bb598d8 --- /dev/null +++ b/src/misc/constants.h @@ -0,0 +1,9 @@ +// Constants.h: Contains configurable constants for SilverMUD. +// Barry Kane, 2022. +#ifndef CONSTANTS_H +#define CONSTANTS_H +#define PORT 5000 +#define MAX 2048 +#define PLAYERCOUNT 64 +#define MAXQUEUELENGTH 2048 +#endif diff --git a/src/misc/inputoutput.c b/src/misc/inputoutput.c index f29572d..0bd1fc6 100644 --- a/src/misc/inputoutput.c +++ b/src/misc/inputoutput.c @@ -1,33 +1,282 @@ -// inputoutput.c: Implementation of input output library for SilverMUD. +// inputoutput.c: Implementation of input/output library for SilverMUD. // Barry Kane, 2022. +#include #include +#include +#include +#include +#include "constants.h" +#include "playerdata.h" #include "inputoutput.h" #include // Sends a message to a given TLS session, wraps the calls to gnutls_write: -void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend) { int returnValue = 0; do { - returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, sizeof(((userMessage*)0)->senderName)); + returnValue = gnutls_record_send(receivingSession, messageToSend->senderName, + sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); do { - returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, sizeof(((userMessage*)0)->messageContent)); + returnValue = gnutls_record_send(receivingSession, messageToSend->messageContent, + sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; } // Recieves a message from a given TLS session, wraps the calls to gnutls_read: -void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage) { int returnValue = 0; do { - returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, sizeof(((userMessage*)0)->senderName)); + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->senderName, + sizeof(((userMessage*)0)->senderName)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); do { - returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, sizeof(((userMessage*)0)->messageContent)); + returnValue = gnutls_record_recv(receiveFromSession, receiveToMessage->messageContent, + sizeof(((userMessage*)0)->messageContent)); } while (returnValue == GNUTLS_E_AGAIN || returnValue == GNUTLS_E_INTERRUPTED); + return returnValue; +} + +outputMessageQueue * createOutputMessageQueue(void) +{ + outputMessageQueue * newQueue = malloc(sizeof(outputMessageQueue)); + newQueue->front = NULL; + newQueue->back = NULL; + newQueue->currentLength = 0; + newQueue->lock = false; + return newQueue; +} + +int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) +{ + // Copy the message into a new output message: + outputMessage * outputMessage = malloc(sizeof(outputMessage)); + + // Allocate the internal userMessage to store the message: + outputMessage->content = malloc(sizeof(userMessage)); + + // Copy the userMessage to the internal userMessage: + strncpy(outputMessage->content->senderName, messageToQueue.senderName, 32); + strncpy(outputMessage->content->messageContent, messageToQueue.messageContent, MAX); + + // We have no targets, NULL sends to all players in an area: + outputMessage->targets[0] = NULL; + + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check that we're not overflowing the queue: + if ((queue->currentLength + 1) > MAXQUEUELENGTH) + { + // Unlock the queue: + queue->lock = false; + return -1; + } + else + { + // If the queue is empty, set the first message as both the front and back of the queue: + if(queue->front == NULL) + { + queue->front = outputMessage; + queue->back = outputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = outputMessage; + queue->back = outputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +int dequeueOutputMessage(outputMessageQueue * queue) +{ + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check the list isn't empty: + if(queue->front == NULL) + { + queue->lock = false; + return -1; + } + + // If there is only one item in the queue: + else if(queue->front == queue->back) + { + free(queue->front->content); + free(queue->front); + queue->front = NULL; + queue->back = NULL; + queue->currentLength--; + queue->lock = false; + return 0; + } + + // Remove the front item: + else + { + outputMessage * messageToDelete = queue->front; + queue->front = queue->front->next; + free(messageToDelete->content); + free(messageToDelete); + queue->currentLength--; + queue->lock = false; + return 0; + } +} + +inputMessageQueue * createInputMessageQueue(void) +{ + inputMessageQueue * newQueue = malloc(sizeof(inputMessageQueue)); + newQueue->front = NULL; + newQueue->back = NULL; + newQueue->currentLength = 0; + newQueue->lock = false; + return newQueue; +} + +int dequeueInputMessage(inputMessageQueue * queue) +{ + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check the list isn't empty: + if(queue->front == NULL) + { + queue->lock = false; + return -1; + } + + // If there is only one item in the queue: + else if(queue->front == queue->back) + { + free(queue->front->content); + free(queue->front); + queue->front = NULL; + queue->back = NULL; + queue->currentLength--; + queue->lock = false; + return 0; + } + + // Remove the front item: + else + { + inputMessage * messageToDelete = queue->front; + queue->front = queue->front->next; + free(messageToDelete->content); + free(messageToDelete); + queue->currentLength--; + queue->lock = false; + return 0; + } +} + +int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer) +{ + // Copy the message into a new input message: + inputMessage * inputMessage = malloc(sizeof(inputMessage)); + + // Allocate the internal userMessage to store the message: + inputMessage->content = malloc(sizeof(userMessage)); + + // Copy the userMessage to the internal userMessage: + strncpy(inputMessage->content->senderName, messageToQueue.senderName, 32); + strncpy(inputMessage->content->messageContent, messageToQueue.messageContent, MAX); + + // We have no targets, NULL sends to all players in an area: + inputMessage->sender = sendingPlayer; + + // Wait for the queue to unlock: + while (queue->lock); + + // Lock the queue: + queue->lock = true; + + // Check that we're not overflowing the queue: + if ((queue->currentLength + 1) > MAXQUEUELENGTH) + { + // Unlock the queue: + queue->lock = false; + return -1; + } + else + { + // If the queue is empty, set the first message as both the front and back of the queue: + if(queue->front == NULL) + { + queue->front = inputMessage; + queue->back = inputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + else + { + queue->back->next = inputMessage; + queue->back = inputMessage; + queue->currentLength++; + + // Unlock the queue: + queue->lock = false; + + return 0; + } + } +} + +void userInputSanatize(char * inputString, int length) +{ + for(int index = 0; index <= length; index++) + { + if(!isprint(inputString[index])) + { + inputString[index] = '\n'; + inputString[index + 1] = '\0'; + break; + } + } + inputString[length - 1] = '\0'; +} + +// Return the front inputMessage from an inputMessageQueue: +inputMessage * peekInputMessage(inputMessageQueue * queue) +{ + return queue->front; +} + +outputMessage * peekOutputMessage(outputMessageQueue * queue) +{ + return queue->front; } diff --git a/src/misc/inputoutput.h b/src/misc/inputoutput.h index 71b1ad9..0726a01 100644 --- a/src/misc/inputoutput.h +++ b/src/misc/inputoutput.h @@ -1,21 +1,104 @@ // inputoutput.h: Header file contatning function prototypes and datastructures // for dealing with input and output. // Barry Kane, 2022. -#ifndef INPUTOUTPUt_H +#ifndef INPUTOUTPUT_H #define INPUTOUTPUT_H +#include +#include +#include +#include "constants.h" +#include "playerdata.h" #include // A message datastructure containing a user/character name and the content: typedef struct userMessage { char senderName[32]; - char messageContent[2048]; + char messageContent[MAX]; } userMessage; -// Sends a message to a given TLS session, wraps the calls to gnutls_write: -void messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); +// ================= +// -=[Message I/O]=- +// ================= -// Recieves a message from a given TLS session, wraps the calls to gnutls_read: -void messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); +// Sends a message to a given TLS session, wraps the calls to gnutls_write: +int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); + +// Receives a message from a given TLS session, wraps the calls to gnutls_read: +int messageReceive(gnutls_session_t receiveFromSession, userMessage * receiveToMessage); + +// =================== +// -=[Output Queue]=-: +// =================== +typedef struct outputMessage outputMessage; +typedef struct outputMessage +{ + outputMessage * next; + playerInfo * targets[PLAYERCOUNT]; + userMessage * content; +} outputMessage; + +// A first-in first-out queue for message output to players: +typedef struct outputMessageQueue +{ + bool lock; + int currentLength; + outputMessage * back; + outputMessage * front; +} outputMessageQueue; + +// Creates and initializes a outputMessageQueue: +outputMessageQueue * createOutputMessageQueue(void); + +// Enqueue a userMessage to an outputMessageQueue: +int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue); +// int queueOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, +// playerInfo * targets, int numberOfTargets); + +// Dequeue the front outputMessage from an outputMessageQueue: +int dequeueOutputMessage(outputMessageQueue * queue); + +// Return the front outputMessage from an outputMessageQueue: +outputMessage * peekOutputMessage(outputMessageQueue * queue); + +// ================== +// -=[Input Queue]=-: +// ================== +typedef struct inputMessage inputMessage; +typedef struct inputMessage +{ + inputMessage * next; + playerInfo * sender; + userMessage * content; +} inputMessage; + +// A first-in first-out queue for message input from players: +typedef struct inputMessageQueue +{ + bool lock; + int currentLength; + inputMessage * back; + inputMessage * front; +} inputMessageQueue; + +// Create a inputMessageQueue: +inputMessageQueue * createInputMessageQueue(void); + +// Enqueue a userMessage to an inputMessageQueue: +int queueInputMessage(inputMessageQueue * queue, userMessage messageToQueue, playerInfo * sendingPlayer); + +// Dequeue the front inputMessage from an inputMessageQueue: +int dequeueInputMessage(inputMessageQueue * queue); + +// Return the front inputMessage from an inputMessageQueue: +inputMessage * peekInputMessage(inputMessageQueue * queue); + +// ====================== +// -=[Input Sanitation]=- +// ====================== + +// Sanatize user input to ensure it's okay to send to the server: +void userInputSanatize(char * inputString, int length); #endif + diff --git a/src/misc/playerdata.h b/src/misc/playerdata.h index f507e9f..9966544 100644 --- a/src/misc/playerdata.h +++ b/src/misc/playerdata.h @@ -1,6 +1,5 @@ // playerdata.h: Header file containing data structures for player data and function // prototypes for interacting with said data. -// Barry Kane, 2021. #ifndef PLAYERDATA_H #define PLAYERDATA_H #include @@ -33,7 +32,7 @@ int movePlayerToArea(playerInfo * player, char * requestedPath); // Create an area given a name and description: playerArea * createArea(char * nameString, char * descriptionString); -// Create a path between two areas given two areas and a string: -int createPath(playerArea * fromArea, playerArea * toArea, char * pathFromString, char * pathToString); +// Create a path between two areas given two areas and two strings: +int createPath(playerArea * fromArea, playerArea * toArea, char * fromDescription, char * toDescription); #endif diff --git a/src/tests/outputQueue-test.c b/src/tests/outputQueue-test.c new file mode 100644 index 0000000..9cc894d --- /dev/null +++ b/src/tests/outputQueue-test.c @@ -0,0 +1,35 @@ +#include "../misc/inputoutput.h" +#include +#include +int main() +{ + userMessage A, B, C; + strncpy(A.senderName, "Bob\0", 32 -1); + strncpy(A.messageContent, "joins the fray!\0", MAX-1); + strncpy(B.senderName, "Alice\0", 32 -1); + strncpy(B.messageContent, "challenges the unknown!\0", MAX -1); + strncpy(C.senderName, "Tom\0", 32 -1); + strncpy(C.messageContent, "Attacks them all!\0", MAX -1); + outputMessageQueue * testQueue = createOutputMessageQueue(); + printf("Queue Created.\n"); + printf("%d", queueOutputMessage(testQueue, A)); + printf("Queued A.\n"); + printf("%d", queueOutputMessage(testQueue, B)); + printf("Queued B.\n"); + printf("%d", queueOutputMessage(testQueue, C)); + printf("Queued C.\n"); + printf("%s\n", testQueue->front->content->senderName); + printf("%s\n", testQueue->front->content->messageContent); + printf("%s\n", testQueue->front->next->content->senderName); + printf("%s\n", testQueue->front->next->content->messageContent); + printf("%s\n", testQueue->front->next->next->content->senderName); + printf("%s\n", testQueue->front->next->next->content->messageContent); + printf("%s\n", testQueue->front->content->senderName); + dequeueOutputMessage(testQueue); + printf("%s\n", testQueue->front->content->senderName); + dequeueOutputMessage(testQueue); + printf("%s\n", testQueue->front->content->senderName); +// dequeueOutputMessage(testQueue); +// printf("%s\n", testQueue->front->content->senderName); + return 0; +}