Start of server thread and command console.

This commit is contained in:
Scott Duensing 2021-11-22 20:25:05 -06:00
parent 3cf71f1525
commit f85322c9d7
14 changed files with 382 additions and 59 deletions

View file

@ -23,3 +23,6 @@ SUBDIRS = \
# font \
# client \
server
HEADERS += \
network.h

View file

@ -36,11 +36,13 @@ HEADERS = \
$$SHARED/thirdparty/memwatch/memwatch.h \
$$SHARED/thirdparty/blowfish-api/blowfish.h \
$$SHARED/thirdparty/ini/src/ini.h \
$$SHARED/thirdparty/enet/include/enet.h \
$$SHARED/array.h \
$$SHARED/log.h \
$$SHARED/memory.h \
$$SHARED/util.h \
src/database.h \
src/network.h \
src/os.h
SOURCES = \
@ -52,7 +54,8 @@ SOURCES = \
$$SHARED/memory.c \
$$SHARED/util.c \
src/database.c \
src/main.c
src/main.c \
src/network.c
LIBS = \
-L/usr/lib/x86_64-linux-gnu/ \

View file

@ -33,8 +33,11 @@ static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
uint8_t dbConnect(char *host, uint16_t port, char *database, char *user, char *password) {
uint8_t reconnect = 1;
if (_sql == NULL) {
_sql = mysql_init(NULL);
mysql_options(_sql, MYSQL_OPT_RECONNECT, (const void *)&reconnect);
if (mysql_real_connect(_sql, host, user, password, database, port, NULL, 0) == NULL) {
logWrite("dbConnect: %s\n", mysql_error(_sql));
return FAIL;

View file

@ -18,37 +18,38 @@
*/
#include <termios.h>
#include <sys/select.h>
#include "os.h"
#include "util.h"
#include "stddclmr.h"
#include "thirdparty/ini/src/ini.h"
#include "database.h"
#include "array.h"
#include "network.h"
#include "database.h"
#include "stddclmr.h"
#include "thirdparty/ini/src/ini.h"
typedef struct HashValueS {
char *string;
int32_t integer;
} HashValueT;
// "Config" items come from the INI file. "Settings" are from the database.
typedef struct HashMapS {
char *key;
HashValueT value;
} HashMapT;
static char *_configServer = NULL;
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 char *_configServer = NULL;
static uint16_t _configPort = 0;
static char *_configDatabase = NULL;
static char *_configUser = NULL;
static char *_configPassword = NULL;
static HashMapT *_settings = NULL;
static void configRead(char *file);
static void configWrite(char *file);
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;
@ -102,52 +103,244 @@ 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 };
while (_running) {
while (enet_host_service(server, &event, 1) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_NONE:
break;
case ENET_EVENT_TYPE_CONNECT:
break;
case ENET_EVENT_TYPE_RECEIVE:
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
break;
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
break;
}
}
}
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;
HashValueT tempValue;
char *configFile = NULL;
uint32_t settingsMaxClients = 0;
uint32_t settingsPortNumber = 0;
uint32_t settingsClientVersion = 0;
ENetHost *server = NULL;
ENetAddress address = { 0 };
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;
memoryStartup(argv[0]);
logOpenByHandle(memoryLogHandleGet());
logWrite("%s", "Kangaroo Punch MultiPlayer DOS Game Server Mark II\n");
logWrite("%s", "Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n");
configFile = utilAppNameWithNewExtensionGet(argv[0], "ini");
configRead(configFile);
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
logWrite("%s", "Kangaroo Punch MultiPlayer DOS Game Server Mark II\n");
logWrite("%s", "Copyright (C) 2020-2021 Scott Duensing scott@kangaroopunch.com\n\n");
if (dbConnect(_configServer, _configPort, _configDatabase, _configUser, _configPassword)) {
tempValue.string = NULL;
if (dbSettingsValueGet("maxClients", &tempValue.integer)) shput(_settings, "maxClients", tempValue);
if (dbSettingsValueGet("portNumber", &tempValue.integer)) shput(_settings, "portNumber", tempValue);
dbDisconnect();
} else {
logWrite("Unable to connect to database.\n");
if (!dbConnect(_configServer, _configPort, _configDatabase, _configUser, _configPassword)) {
utilDie("Unable to connect to database.\n");
}
// Show hashmap contents.
for (size_t i=0; i<shlenu(_settings); i++) {
if (_settings[i].value.string) {
logWrite("%s: %s\n", _settings[i].key, _settings[i].value.string);
} else {
logWrite("%s: %d\n", _settings[i].key, _settings[i].value.integer);
// Fetch settings needed to start server.
if (!dbSettingsValueGet("maxClients", (int32_t *)&settingsMaxClients)) utilDie("Unable to load maxClients.\n");
if (!dbSettingsValueGet("portNumber", (int32_t *)&settingsPortNumber)) utilDie("Unable to load portNumber.\n");
if (!dbSettingsValueGet("clientVersion", (int32_t *)&settingsClientVersion)) utilDie("Unable to load clientVersion.\n");
// Set up listening socket.
address.host = ENET_HOST_ANY;
address.port = settingsPortNumber;
server = enet_host_create (&address, /* the address to bind the server host to */
settingsMaxClients, /* allow up to 32 clients and/or outgoing connections */
1, /* allow up to 2 channels to be used, 0 and 1 */
0, /* assume any amount of incoming bandwidth */
0 /* assume any amount of outgoing bandwidth */
);
if (server == NULL) utilDie("Unable to open server listening port.\n");
// Start server thread.
if (pthread_attr_init(&serverThreadAttributes) != 0) utilDie("Unable to create server thread attributes.\n");
pthread_attr_setdetachstate(&serverThreadAttributes, PTHREAD_CREATE_JOINABLE);
if (pthread_create(&serverThreadHandle, &serverThreadAttributes, serverThread, (void *)server) != 0) utilDie("Unable to start server thread.\n");
logWrite("Server online.\n");
// Console.
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("<CANCEL>\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("<OVERFLOW>\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();
// Free hashmap storage.
for (size_t i=0; i<shlenu(_settings); i++) {
if (_settings[i].value.string) DEL(_settings[i].value.string);
}
shfree(_settings);
_settings = NULL;
// Wait for all running threads to shut down.
logWrite("Shutting down.\n");
pthread_join(serverThreadHandle, &serverThreadStatus);
// Shut down.
enet_host_destroy(server);
dbDisconnect();
configWrite(configFile);
DEL(configFile);
logWrite("Shutdown complete.\n");
logClose();
memoryShutdown();
return 0;
pthread_exit(NULL);
}

22
server/src/network.c Normal file
View file

@ -0,0 +1,22 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#define ENET_IMPLEMENTATION
#include "network.h"

29
server/src/network.h Normal file
View file

@ -0,0 +1,29 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#ifndef NETWORK_H
#define NETWORK_H
#include "os.h"
#include "thirdparty/enet/include/enet.h"
#endif // NETWORK_H

View file

@ -29,6 +29,7 @@
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <pthread.h>
// Should be after system headers in this file.
#define MEMORY_CHECK_ENABLED

View file

@ -22,3 +22,28 @@
#define STB_DS_IMPLEMENTATION
#include "thirdparty/stb_ds.h"
void arrayHashMapDump(HashMapT *hashMap) {
// Show hashmap contents.
for (size_t i=0; i<shlenu(hashMap); i++) {
if (hashMap[i].value.string) {
logWrite("%s: %s\n", hashMap[i].key, hashMap[i].value.string);
} else {
logWrite("%s: %d\n", hashMap[i].key, hashMap[i].value.integer);
}
}
}
void arrayHashMapFree(HashMapT **hashMap) {
HashMapT *h = *hashMap;
// Free hashmap storage.
for (size_t i=0; i<shlenu(h); i++) {
if (h[i].value.string) DEL(h[i].value.string);
}
shfree(h);
h = NULL;
*hashMap = h;
}

View file

@ -26,4 +26,19 @@
#include "thirdparty/stb_ds.h"
typedef struct HashValueS {
char *string;
int32_t integer;
} HashValueT;
typedef struct HashMapS {
char *key;
HashValueT value;
} HashMapT;
void arrayHashMapDump(HashMapT *hashMap);
void arrayHashMapFree(HashMapT **hashMap);
#endif // ARRAY_H

View file

@ -69,3 +69,17 @@ void logWrite(char *format, ...) {
va_end(args);
}
void logWriteToFileOnly(char *format, ...) {
va_list args;
va_start(args, format);
if (_log) {
vfprintf(_log, format, args);
fflush(_log);
}
va_end(args);
}

View file

@ -29,6 +29,7 @@ uint8_t logOpen(char *filename, uint8_t append);
void logOpenByHandle(FILE *handle);
void logClose(void);
void logWrite(char *format, ...);
void logWriteToFileOnly(char *format, ...);
#endif // LOG_H

View file

@ -240,8 +240,8 @@ extern "C" {
typedef struct _ENetPacket ENetPacket;
typedef struct _ENetCallbacks {
void *(ENET_CALLBACK *malloc) (size_t size);
void (ENET_CALLBACK *free) (void *memory);
void *(ENET_CALLBACK *emalloc) (size_t size);
void (ENET_CALLBACK *efree) (void *memory);
void (ENET_CALLBACK *no_memory) (void);
ENetPacket *(ENET_CALLBACK *packet_create) (const void *data, size_t dataLength, enet_uint32 flags);
@ -1243,13 +1243,13 @@ extern "C" {
return -1;
}
if (inits->malloc != NULL || inits->free != NULL) {
if (inits->malloc == NULL || inits->free == NULL) {
if (inits->emalloc != NULL || inits->efree != NULL) {
if (inits->emalloc == NULL || inits->efree == NULL) {
return -1;
}
callbacks.malloc = inits->malloc;
callbacks.free = inits->free;
callbacks.emalloc = inits->emalloc;
callbacks.efree = inits->efree;
}
if (inits->no_memory != NULL) {
@ -1273,7 +1273,7 @@ extern "C" {
}
void * enet_malloc(size_t size) {
void *memory = callbacks.malloc(size);
void *memory = callbacks.emalloc(size);
if (memory == NULL) {
callbacks.no_memory();
@ -1283,7 +1283,7 @@ extern "C" {
}
void enet_free(void *memory) {
callbacks.free(memory);
callbacks.efree(memory);
}
// =======================================================================//

View file

@ -49,3 +49,16 @@ char *utilAppNameWithNewExtensionGet(char *appName, char *extension) {
return newName;
}
void utilDie(const char *why, ...) {
va_list args;
char msg[2048];
va_start(args, why);
vsprintf(msg, why, args);
va_end(args);
logWrite("DIE: %s", msg);
exit(1);
}

View file

@ -26,6 +26,7 @@
char *utilAppNameWithNewExtensionGet(char *appName, char *extension);
void utilDie(const char *why, ...);
#endif // UTIL_H