diff --git a/server/server.pro b/server/server.pro
index f34b7c8..1e5cc4a 100644
--- a/server/server.pro
+++ b/server/server.pro
@@ -19,7 +19,9 @@
TEMPLATE = app
CONFIG -= qt
-CONFIG += c11
+CONFIG += \
+ console \
+ c11
DESTDIR = $$OUT_PWD/bin
SHARED = $$PWD/../shared
@@ -41,6 +43,8 @@ HEADERS = \
$$SHARED/log.h \
$$SHARED/memory.h \
$$SHARED/util.h \
+ src/client.h \
+ src/console.h \
src/database.h \
src/network.h \
src/os.h
@@ -53,6 +57,8 @@ SOURCES = \
$$SHARED/log.c \
$$SHARED/memory.c \
$$SHARED/util.c \
+ src/client.c \
+ src/console.c \
src/database.c \
src/main.c \
src/network.c
diff --git a/server/src/client.c b/server/src/client.c
new file mode 100644
index 0000000..1c63b26
--- /dev/null
+++ b/server/src/client.c
@@ -0,0 +1,32 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+#include
+
+#include "client.h"
+#include "network.h"
+
+
+void *clientThread(void *data) {
+
+ ClientThreadT *client = (ClientThreadT *)data;
+
+ pthread_exit(NULL);
+}
diff --git a/server/src/client.h b/server/src/client.h
new file mode 100644
index 0000000..8d32b99
--- /dev/null
+++ b/server/src/client.h
@@ -0,0 +1,41 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+#ifndef CLIENT_H
+#define CLIENT_H
+
+
+#include "os.h"
+#include "network.h"
+
+
+typedef struct ClientThreadS {
+ uint64_t threadIndex;
+ pthread_t threadHandle;
+ pthread_attr_t threadAttributes;
+ ENetPeer *peer;
+ uint8_t running;
+} ClientThreadT;
+
+
+void *clientThread(void *data);
+
+
+#endif // CLIENT_H
diff --git a/server/src/console.c b/server/src/console.c
new file mode 100644
index 0000000..3e7a4f8
--- /dev/null
+++ b/server/src/console.c
@@ -0,0 +1,222 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+#include
+#include
+#include
+
+#include "console.h"
+#include "array.h"
+#include "util.h"
+
+
+static uint8_t _running = 1;
+static struct termios _termios = { 0 };
+static char **_consoleMessageQueue = NULL;
+static pthread_mutex_t _messageQueueMutex = PTHREAD_MUTEX_INITIALIZER;
+
+static uint8_t getch(void);
+static uint8_t kbhit(void);
+static void sendToConsole(const char *message, ...);
+static void terminalModeConioSet(void);
+static void terminalModeOriginalSet(void);
+
+
+void consoleMessageQueue(const char *message, ...) {
+ va_list args = { 0 };
+ char buffer[2048] = { 0 };
+ char *newMessage = NULL;
+
+ va_start(args, message);
+ vsprintf(buffer, message, args);
+ va_end(args);
+
+ newMessage = strdup(buffer);
+
+ pthread_mutex_lock(&_messageQueueMutex);
+ arrput(_consoleMessageQueue, newMessage);
+ pthread_mutex_unlock(&_messageQueueMutex);
+}
+
+
+void consoleRun(void) {
+ uint8_t commandEntering = 0;
+ char command[256] = { 0 };
+ uint16_t commandIndex = 0;
+ char c = 0;
+ uint8_t commandOk = 0;
+ struct timespec remaining = { 0, 0 };
+ struct timespec sleepTime = { 0, 1000000000/4 };
+
+ if (pthread_mutex_init(&_messageQueueMutex, NULL)) utilDie("Unable to create console message queue mutex.\n");
+
+ terminalModeConioSet();
+ while (_running) {
+
+ if (kbhit()) {
+
+ // Is this a new command?
+ if (!commandEntering) {
+ // Show prompt.
+ printf(">");
+ // Command is being entered.
+ commandEntering = 1;
+ }
+
+ // Read key.
+ c = getch();
+
+ switch (c) {
+ // Backspace
+ case 8:
+ case 127:
+ break;
+
+ // ESC
+ case 27:
+ // Abort command entry.
+ printf("\n\r");
+ commandIndex = 0;
+ command[0] = 0;
+ commandEntering = 0;
+ break;
+
+ // ENTER
+ case 13:
+ printf("\n\r");
+ logWriteToFileOnly(">%s\n", command);
+ commandOk = 0;
+ if (!strcasecmp(command, "HELP") || !strcasecmp(command, "?")) {
+ sendToConsole("HELP or ? - This message.\n");
+ sendToConsole("SHUTDOWN - Stop the server.\n");
+ commandOk = 1;
+ }
+ if (!strcasecmp(command, "SHUTDOWN")) {
+ _running = 0;
+ commandOk = 1;
+ }
+ // Did we grok it?
+ if (!commandOk) {
+ sendToConsole("Unknown command! Type HELP (or ?) for help.\n\r");
+ }
+ commandIndex = 0;
+ command[0] = 0;
+ commandEntering = 0;
+ break;
+
+ default:
+ // Add to command.
+ command[commandIndex++] = c;
+ command[commandIndex] = 0;
+ printf("%c", c);
+ // Overflow?
+ if (commandIndex == 255) {
+ printf("\n\r");
+ commandIndex = 0;
+ command[0] = 0;
+ commandEntering = 0;
+ }
+ break;
+ }
+
+ fflush(stdout);
+ }
+
+ // Only do this if we're not typing on the console.
+ if (!commandEntering) {
+ // Don't eat all the CPU.
+ nanosleep(&remaining, &sleepTime);
+
+ // Messages to display?
+ pthread_mutex_lock(&_messageQueueMutex);
+ if (arrlenu(_consoleMessageQueue) > 0) {
+ sendToConsole("%s", _consoleMessageQueue[0]);
+ arrdel(_consoleMessageQueue, 0);
+ }
+ pthread_mutex_unlock(&_messageQueueMutex);
+ }
+ }
+ terminalModeOriginalSet();
+
+ pthread_mutex_destroy(&_messageQueueMutex);
+}
+
+
+static uint8_t getch(void) {
+ int r = 0;
+ uint8_t c = 0;
+
+ if ((r = read(0, &c, sizeof(c))) < 0) {
+ return r;
+ } else {
+ return c;
+ }
+}
+
+
+static uint8_t kbhit(void) {
+ struct timeval tv = { 0L, 0L };
+ fd_set fds = { 0 };
+
+ FD_ZERO(&fds);
+ FD_SET(0, &fds);
+
+ return select(1, &fds, NULL, NULL, &tv) > 0;
+}
+
+
+static void sendToConsole(const char *message, ...) {
+ va_list args = { 0 };
+ char buffer[2048] = { 0 };
+
+ va_start(args, message);
+
+ vsprintf(buffer, message, args);
+ printf("%s", buffer);
+ if (buffer[strlen(buffer) - 1] == '\n') {
+ // Console needs a CR with its LF.
+ printf("\r");
+ }
+ fflush(stdout);
+
+ logWriteToFileOnly("%s", buffer);
+
+ va_end(args);
+}
+
+
+static void terminalModeConioSet(void) {
+ struct termios new_termios = { 0 };
+
+ // https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input
+
+ // Take two copies - one for now, one for later.
+ tcgetattr(0, &_termios);
+ memcpy(&new_termios, &_termios, sizeof(new_termios));
+ cfmakeraw(&new_termios);
+ tcsetattr(0, TCSANOW, &new_termios);
+}
+
+
+static void terminalModeOriginalSet(void) {
+ tcsetattr(0, TCSANOW, &_termios);
+}
+
+
diff --git a/server/src/console.h b/server/src/console.h
new file mode 100644
index 0000000..5bb4579
--- /dev/null
+++ b/server/src/console.h
@@ -0,0 +1,32 @@
+/*
+ * Kangaroo Punch MultiPlayer Game Server Mark II
+ * Copyright (C) 2020-2021 Scott Duensing
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+
+#include "os.h"
+
+
+void consoleMessageQueue(const char *message, ...);
+void consoleRun(void);
+
+
+#endif // CONSOLE_H
diff --git a/server/src/main.c b/server/src/main.c
index d9d77f0..97d1d8b 100644
--- a/server/src/main.c
+++ b/server/src/main.c
@@ -18,12 +18,11 @@
*/
-#include
-#include
-
#include "os.h"
#include "util.h"
#include "array.h"
+#include "client.h"
+#include "console.h"
#include "network.h"
#include "database.h"
#include "stddclmr.h"
@@ -38,18 +37,14 @@ static uint16_t _configPort = 0;
static char *_configDatabase = NULL;
static char *_configUser = NULL;
static char *_configPassword = NULL;
+
static uint8_t _running = 1;
-static struct termios _termios = { 0 };
static void configRead(char *file);
static void configWrite(char *file);
-static uint8_t getch(void);
-static uint8_t kbhit(void);
-static void sendToConsole(const char *message, ...);
static void *serverThread(void *data);
-static void terminalModeConioSet(void);
-static void terminalModeOriginalSet(void);
+
static void configRead(char *file) {
ini_t *ini = NULL;
@@ -103,52 +98,15 @@ static void configWrite(char *file) {
}
-static uint8_t getch(void) {
- int r = 0;
- uint8_t c = 0;
-
- if ((r = read(0, &c, sizeof(c))) < 0) {
- return r;
- } else {
- return c;
- }
-}
-
-
-static uint8_t kbhit(void) {
- struct timeval tv = { 0L, 0L };
- fd_set fds = { 0 };
-
- FD_ZERO(&fds);
- FD_SET(0, &fds);
-
- return select(1, &fds, NULL, NULL, &tv) > 0;
-}
-
-
-static void sendToConsole(const char *message, ...) {
- va_list args;
- char buffer[2048];
-
- va_start(args, message);
-
- vsprintf(buffer, message, args);
- printf("%s", buffer);
- if (buffer[strlen(buffer) - 1] == '\n') {
- // Console needs a CR with its LF.
- printf("\r");
- }
- fflush(stdout);
-
- logWriteToFileOnly("%s", buffer);
-
- va_end(args);
-}
-
-
static void *serverThread(void *data) {
- ENetHost *server = (ENetHost *)data;
- ENetEvent event = { 0 };
+ ENetHost *server = (ENetHost *)data;
+ ENetEvent event = { 0 };
+ ClientThreadT **clientList = NULL;
+ ClientThreadT *client = NULL;
+ uint64_t nextIndex = 0;
+ size_t i = 0;
+ void *status = NULL;
+ char buffer[2048] = { 0 };
while (_running) {
while (enet_host_service(server, &event, 1) > 0) {
@@ -157,6 +115,23 @@ static void *serverThread(void *data) {
break;
case ENET_EVENT_TYPE_CONNECT:
+ // Create new client.
+ NEW(ClientThreadT, client);
+ if (!client) utilDie("Unable to allocate new client.\n");
+ client->threadIndex = nextIndex++;
+ client->peer = event.peer;
+ client->running = 1;
+ // Keep our index in the peer data for later.
+ event.peer->data = (void *)client->threadIndex;
+ // Make new thread for this client.
+ if (pthread_attr_init(&client->threadAttributes) != 0) utilDie("Unable to create client thread attributes.\n");
+ pthread_attr_setdetachstate(&client->threadAttributes, PTHREAD_CREATE_JOINABLE);
+ if (pthread_create(&client->threadHandle, &client->threadAttributes, clientThread, (void *)client) != 0) utilDie("Unable to start client thread.\n");
+ // Add to our client list.
+ arrput(clientList, client);
+ // Tell the console.
+ enet_address_get_host_ip(&event.peer->address, buffer, 2047);
+ consoleMessageQueue("%ld: %s connected.\n", client->threadIndex, buffer);
break;
case ENET_EVENT_TYPE_RECEIVE:
@@ -164,36 +139,40 @@ static void *serverThread(void *data) {
break;
case ENET_EVENT_TYPE_DISCONNECT:
- break;
-
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
+ // Find our client.
+ for (i=0; idata == clientList[i]->threadIndex) {
+ // Stop client processing.
+ clientList[i]->running = 0;
+ pthread_join(clientList[i]->threadHandle, &status);
+ // Tell the console.
+ enet_address_get_host_ip(&event.peer->address, buffer, 2047);
+ consoleMessageQueue("%ld: %s disconnected.\n", clientList[i]->threadIndex, buffer);
+ // Take it out of the client list.
+ arrdel(clientList, i);
+ break;
+ }
+ }
break;
}
}
}
+ // Anyone still running, be rude. Nuke 'em.
+ for (i=0; ipeer);
+ // Stop client processing.
+ clientList[i]->running = 0;
+ pthread_join(clientList[i]->threadHandle, &status);
+ }
+ arrfree(clientList);
+
pthread_exit(NULL);
}
-static void terminalModeConioSet(void) {
- struct termios new_termios = { 0 };
-
- // https://stackoverflow.com/questions/448944/c-non-blocking-keyboard-input
-
- // Take two copies - one for now, one for later.
- tcgetattr(0, &_termios);
- memcpy(&new_termios, &_termios, sizeof(new_termios));
- cfmakeraw(&new_termios);
- tcsetattr(0, TCSANOW, &new_termios);
-}
-
-
-static void terminalModeOriginalSet(void) {
- tcsetattr(0, TCSANOW, &_termios);
-}
-
-
int main(int argc, char *argv[]) {
char *configFile = NULL;
@@ -205,13 +184,6 @@ int main(int argc, char *argv[]) {
pthread_t serverThreadHandle = { 0 };
pthread_attr_t serverThreadAttributes = { 0 };
void *serverThreadStatus = NULL;
- uint8_t commandEntering = 0;
- char command[256] = { 0 };
- uint16_t commandIndex = 0;
- char c = 0;
- uint8_t commandOk = 0;
- struct timespec remaining = { 0, 0 };
- struct timespec sleepTime = { 0, 1000000000/4 };
(void)argc;
@@ -249,85 +221,11 @@ int main(int argc, char *argv[]) {
logWrite("Server online.\n");
- // Console.
- terminalModeConioSet();
- while (_running) {
+ // Run Console.
+ consoleRun();
- if (kbhit()) {
-
- // Is this a new command?
- if (!commandEntering) {
- // Show prompt.
- printf(">");
- // Command is being entered.
- commandEntering = 1;
- }
-
- // Read key.
- c = getch();
-
- switch (c) {
- // Backspace
- case 8:
- case 127:
- break;
-
- // ESC
- case 27:
- // Abort command entry.
- printf("\n\r");
- commandIndex = 0;
- command[0] = 0;
- commandEntering = 0;
- break;
-
- // ENTER
- case 13:
- printf("\n\r");
- logWriteToFileOnly(">%s\n", command);
- commandOk = 0;
- if (!strcasecmp(command, "HELP") || !strcasecmp(command, "?")) {
- sendToConsole("HELP or ? - This message.\n");
- sendToConsole("SHUTDOWN - Stop the server.\n");
- commandOk = 1;
- }
- if (!strcasecmp(command, "SHUTDOWN")) {
- _running = 0;
- commandOk = 1;
- }
- // Did we grok it?
- if (!commandOk) {
- sendToConsole("Unknown command! Type HELP (or ?) for help.\n\r");
- }
- commandIndex = 0;
- command[0] = 0;
- commandEntering = 0;
- break;
-
- default:
- // Add to command.
- command[commandIndex++] = c;
- command[commandIndex] = 0;
- printf("%c", c);
- // Overflow?
- if (commandIndex == 255) {
- printf("\n\r");
- commandIndex = 0;
- command[0] = 0;
- commandEntering = 0;
- }
- break;
- }
-
- fflush(stdout);
- }
-
- // Only do this if we're not typing on the console.
- if (!commandEntering) {
- nanosleep(&remaining, &sleepTime);
- }
- }
- terminalModeOriginalSet();
+ // Console closed. Stop server.
+ _running = 0;
// Wait for all running threads to shut down.
logWrite("Shutting down.\n");