Start of server thread and command console.
This commit is contained in:
parent
3cf71f1525
commit
f85322c9d7
14 changed files with 382 additions and 59 deletions
|
@ -23,3 +23,6 @@ SUBDIRS = \
|
|||
# font \
|
||||
# client \
|
||||
server
|
||||
|
||||
HEADERS += \
|
||||
network.h
|
||||
|
|
|
@ -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/ \
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
22
server/src/network.c
Normal 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
29
server/src/network.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
14
shared/log.c
14
shared/log.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
16
shared/thirdparty/enet/include/enet.h
vendored
16
shared/thirdparty/enet/include/enet.h
vendored
|
@ -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);
|
||||
}
|
||||
|
||||
// =======================================================================//
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
|
||||
char *utilAppNameWithNewExtensionGet(char *appName, char *extension);
|
||||
void utilDie(const char *why, ...);
|
||||
|
||||
|
||||
#endif // UTIL_H
|
||||
|
|
Loading…
Add table
Reference in a new issue