commit 618b2144e3410df064c76f5c110369bcd3358eff Author: Barry Kane Date: Sun Aug 15 19:42:37 2021 +0100 Initial release of SilverMUD: Features Added: - Server capable of receiving and sending messages to up to 64 clients. - Client capable of sending and receiving messages from the server, multi-threaded. - Text effect library with one effect. Features Changed: - None, initial release. Features Removed: - None, initial release. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..606bbff --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +clientsrc = $(wildcard src/misc/*.c) \ + src/SilverMUDClient.c +clientobj = $(clientsrc:.c=.o) +serversrc = $(wildcard src/misc/*.c) \ + src/SilverMUDServer.c +serverobj = $(serversrc:.c=.o) +CLIENTLDFLAGS= -lpthread + +SilverMUDClient: $(clientobj) + gcc -o $@ $^ $(CLIENTLDFLAGS) + +SilverMUDServer: $(serverobj) + gcc -o $@ $^ + +.PHONY: clean +clean: + rm -f $(clientobj) $(serverobj) SilverMUDClient SilverMUDServer + +all: SilverMUDClient SilverMUDServer diff --git a/src/SilverMUDClient.c b/src/SilverMUDClient.c new file mode 100644 index 0000000..ea264fc --- /dev/null +++ b/src/SilverMUDClient.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc/texteffects.h" +#define MAX 1024 +#define PORT 5000 +#define SA struct sockaddr + +void * messageSender(void * sockfd) +{ + char sendBuffer[MAX]; + int characterindex; + + while (1) + { + bzero(sendBuffer, MAX); + printf("COMM-LINK> "); + fgets(sendBuffer, MAX, stdin); + if(sendBuffer[0] != '\n'); + { + write((long)sockfd, sendBuffer, MAX); + } + } +} + + +void * messageReceiver(void * sockfd) +{ + char receiveBuffer[MAX]; + while (1) + { + read((long)sockfd, receiveBuffer, MAX); + slowprint("\nUSER-MESSAGE: ", 8000); + slowprint(receiveBuffer, 8000); + slowprint("\nCOMM-LINK (CONT.)> ", 8000); + bzero(receiveBuffer, MAX); + } +} + +int main(int argc, char **argv) +{ + int sockfd, connfd; + struct sockaddr_in servaddr, cli; + pthread_t messagingThread; + + // Give me a socket, and make sure it's working: + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd == -1) + { + printf("Socket creation failed.\n"); + exit(0); + } + else + { + slowprint("Socket successfully created.\n", 8000); + } + bzero(&servaddr, sizeof(servaddr)); + + // Set our IP Address and port. Default to localhost for testing: + servaddr.sin_family = AF_INET; + if (argc == 1) + { + servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); + } + else + { + servaddr.sin_addr.s_addr = inet_addr(argv[1]); + } + servaddr.sin_port = htons(PORT); + + // Connect the server and client sockets, Kronk: + if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) + { + slowprint("Connection with the Silverkin Industries Comm-Link Server Failed:\nPlease contact your service representative.\n", 8000); + exit(0); + } + else + { + slowprint("Connected to the Silverkin Industries Comm-Link Server:\nHave a pleasant day.\n", 8000); + } + + // Run a thread to send messages, and use main to recieve. + pthread_create(&messagingThread, NULL, messageSender, (void *)(long)sockfd); + messageReceiver((void *)(long)sockfd); + + // Close the socket. + close(sockfd); +} diff --git a/src/SilverMUDServer.c b/src/SilverMUDServer.c new file mode 100644 index 0000000..df4783c --- /dev/null +++ b/src/SilverMUDServer.c @@ -0,0 +1,185 @@ +// Silverkin Industries Comm-Link Server, Engineering Sample Alpha 0.1. +// PROJECT CODENAME: WHAT DO I PAY YOU FOR? | Level-3 Clearance. +// Barry Kane, 2021 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc/texteffects.h" +const int PORT = 5000; +const int MAX = 1024; +typedef struct sockaddr sockaddr; + +int main() +{ + int socketFileDesc, connectionFileDesc, length, clientsAmount, + socketCheck, activityCheck, readLength; + int clientSockets[64]; + int maxClients = 64; + char receiveBuffer[MAX]; + fd_set connectedClients; + struct sockaddr_in serverAddress, clientAddress; + + // Give an intro: Display the Silverkin Industries logo and splash text. + slowprint(logostring, 3000); + slowprint("\n--==== \033[33;40mSILVERKIN INDUSTRIES\033[0m COMM-LINK SERVER ====--\nVersion Alpha 0.1\n", 5000); + + // Initialize the sockets to 0, so we don't crash. + for (int index = 0; index < maxClients; index++) + { + clientSockets[index] = 0; + } + + // Get a socket and make sure we actually get one. + socketFileDesc = socket(AF_INET, SOCK_STREAM, 0); + if (socketFileDesc == -1) + { + perror("Socket creation is \033[33;40mRED.\033[0m Aborting launch.\n"); + exit(0); + } + + else + { + slowprint(" Socket creation is \033[32;40mGREEN.\033[0m\n", 5000); + } + + bzero(&serverAddress, sizeof(serverAddress)); + + // Assign IP and port: + serverAddress.sin_family = AF_INET; + serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddress.sin_port = htons(PORT); + + // Binding newly created socket to given IP, and checking it works: + if ((bind(socketFileDesc, (sockaddr*)&serverAddress, sizeof(serverAddress))) != 0) + { + perror("Socket binding is \033[33;40mRED.\033[0m Aborting launch.\n"); + exit(0); + } + else + { + slowprint(" Socket binding is \033[32;40mGREEN.\033[0m\n", 5000); + } + + // Let's start listening: + if ((listen(socketFileDesc, 64)) != 0) + { + perror("Server listening is \033[33;40mRED.\033[0m Aborting launch.\n"); + exit(0); + } + else + { + slowprint(" Server listening is \033[32;40mGREEN.\033[0m\n", 5000); + } + length = sizeof(clientAddress); + + //connectionFileDesc = accept(socketFileDesc, (sockaddr*)&clientAddress, &length); + // Accept the data packet from client and verification + while (1) + { + FD_ZERO(&connectedClients); + FD_SET(socketFileDesc, &connectedClients); + clientsAmount = socketFileDesc; + bzero(receiveBuffer, sizeof(receiveBuffer)); + for (int i = 0; i < maxClients; i++) + { + // Just get the one we're working with to another name: + socketCheck = clientSockets[i]; + + // If it's working, bang it into the list: + if(socketCheck > 0) + { + FD_SET(socketCheck, &connectedClients); + } + // The amount of clients is needed for select(): + if(socketCheck > clientsAmount) + { + clientsAmount = socketCheck; + } + } + + // See if a connection is ready to be interacted with: + activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); + + // Check if select() worked: + if ((activityCheck < 0) && (errno != EINTR)) + { + perror("Error in select(), retrying.\n"); + } + + // If it's the master socket selected, there is a new connection: + if (FD_ISSET(socketFileDesc, &connectedClients)) + { + if ((connectionFileDesc = accept(socketFileDesc, + (struct sockaddr *)&clientAddress, (socklen_t*)&length))<0) + { + perror("Failed to accept connection. Aborting.\n"); + exit(EXIT_FAILURE); + } + + // Print new connection details: + printf("Client connected: Socket file descriptor: #%d, IP address: %s, Port: %d.\n", + connectionFileDesc, inet_ntoa(clientAddress.sin_addr) , ntohs + (clientAddress.sin_port)); + + // See if we can put in the client: + for (int i = 0; i < maxClients; i++) + { + // When there is an empty slot, pop it in: + if( clientSockets[i] == 0 ) + { + clientSockets[i] = connectionFileDesc; + printf("Adding to list of sockets as %d.\n" , i); + break; + } + } + } + else + { + // Otherwise, it's a client socket to be interacted with: + for (int i = 0; i < maxClients; i++) + { + socketCheck = clientSockets[i]; + + if (FD_ISSET(socketCheck, &connectedClients)) + { + //Check if it was for closing, and also read the incoming message + explicit_bzero(receiveBuffer, sizeof(receiveBuffer)); + readLength = read(socketCheck, receiveBuffer, sizeof(receiveBuffer)); + if (readLength == 0) + { + // Somebody disconnected , get his details and print: + getpeername(socketCheck, (struct sockaddr*)&clientAddress, (socklen_t*)&length); + printf("Client disconnected: IP Address: %s, Port: %d.\n", + inet_ntoa(clientAddress.sin_addr) , ntohs(clientAddress.sin_port)); + + // Close the socket and mark as 0 in list for reuse: + close(socketCheck); + clientSockets[i] = 0; + } + + // Echo back the message that came in: + else + { + printf("%d: %s", clientSockets[i], receiveBuffer); + fflush(stdout); + for (int sendIndex = 0; sendIndex < clientsAmount; sendIndex++) + { + if(sendIndex != i && clientSockets[sendIndex] != STDIN_FILENO && clientSockets[sendIndex] != STDOUT_FILENO && clientSockets[sendIndex] != STDERR_FILENO) + { + write(clientSockets[sendIndex], receiveBuffer, sizeof(receiveBuffer)); + } + } + bzero(receiveBuffer, sizeof(receiveBuffer)); + } + } + } + } + } +} diff --git a/src/misc/texteffects.c b/src/misc/texteffects.c new file mode 100644 index 0000000..23f03b8 --- /dev/null +++ b/src/misc/texteffects.c @@ -0,0 +1,17 @@ +// texteffects.c: Implementation of text effect library for SilverMUD. +// Barry Kane, 2021. +#include +#include + +void slowprint(char * stringToPrint, int delay) +{ + int characterIndex = 0; + while(stringToPrint[characterIndex] != '\0') + { + putchar(stringToPrint[characterIndex]); + // Flush the buffer so there's no line buffering. + fflush(stdout); + usleep(delay); + characterIndex++; + } +} diff --git a/src/misc/texteffects.h b/src/misc/texteffects.h new file mode 100644 index 0000000..b04a84b --- /dev/null +++ b/src/misc/texteffects.h @@ -0,0 +1,11 @@ +#ifndef TEXTEFFECTS_H_ +#define TEXTEFFECTS_H_ +#include + +// A fancy, character by character print. Similar to a serial terminal with lower baud rate. +void slowprint(char * stringToPrint, int delay); + +// A string containing an ASCII art version of the Silverkin Industries logo. +char * logostring = " ///////\n //////////////////////////////////////////\n ///////////////////////////////////////////////////////////\n ////////// ////////////////////////////\n ### # # # # ##### ### # # # # # /////////////////\n ### # # # # ## # # ## # ## # //////////////\n ## # # # # # ### # # # # # # /////////\n #### # ### # ##### # # # # # # ## ///////\n # ## # ##### # # ### ### ### # ##### ### ////// \n # # # # # # # # ## # # # # ## ## ////\n # # # # # # # # ## # ### # # ## //\n # # ### ##### ##### ### # # # # #### ### //\n"; + +#endif