Server config load and graceful client exit

This commit is contained in:
2026-01-26 01:25:16 +01:00
parent 904efbd9b9
commit fe703b6ddd
7 changed files with 252 additions and 26 deletions
+1
View File
@@ -1 +1,2 @@
build
.conf
+2 -1
View File
@@ -1,5 +1,6 @@
CC = gcc
CFLAGS = -Wall -I.
LDFLAGS = -lconfig
all: client server
@@ -9,7 +10,7 @@ client:
server:
mkdir -p build
$(CC) $(CFLAGS) -o build/server server.c util/util.c
$(CC) $(CFLAGS) -o build/server server.c util/util.c $(LDFLAGS)
clean:
rm -f client server
+48 -6
View File
@@ -2,12 +2,21 @@
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <unistd.h>
#include "commands.h"
#include "signal.h"
// Wanted a graceful exit so need to keep this in global state
static int client_queue_id_global = -1;
static int server_queue_id_global = -1;
static char client_id_global[COMMAND_LENGTH];
static pid_t heartbeat_pid = -1;
static pid_t input_pid = -1;
// Make a macro for this cause I'm not typing sizeof twice every time
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
@@ -30,6 +39,27 @@ static void cleanup_queue(int qid) {
}
}
void cleanup_and_exit(int sig) {
printf("\nLogging out... ");
if (server_queue_id_global != -1) {
msgbuf_t logout = {.mtype = Logout, .sender = ""};
strncpy(logout.sender, client_id_global, COMMAND_LENGTH - 1);
msgsnd(server_queue_id_global, &logout, sizeof(logout) - sizeof(long), 0);
}
// Burn the orphanage
if (heartbeat_pid > 0)
kill(heartbeat_pid, SIGTERM);
if (input_pid > 0)
kill(input_pid, SIGTERM);
cleanup_queue(client_queue_id_global);
printf("Bye!\n");
exit(0);
}
static void input_loop(int server_queue_id, const char *client_id) {
while (1) {
// Minimum length plus extra space fore command name and seperators
@@ -88,9 +118,8 @@ static void input_loop(int server_queue_id, const char *client_id) {
}
} else if (strncmp(buffer, "/mute ", 6) == 0) {
// handle mute
} else if (strcmp(buffer, "/quit") == 0) {
// TODO: Would be nice to have a way to gracefully logout and not just
// depend on heartbeats, maybe
} else if (strcmp(buffer, "/quit") == 0 || strcmp(buffer, "/exit") == 0 || strcmp(buffer, "/q") == 0) {
return;
} else if (strncmp(buffer, "/inbox", 8) == 0) {
msgbuf_t msg = {.mtype = Inbox, .sender = ""};
strncpy(msg.sender, client_id, COMMAND_LENGTH - 1);
@@ -163,16 +192,29 @@ int main(int argc, char *argv[]) {
printf("Login accepted. Welcome, %s!\n", resp.command);
if (fork() == 0) {
strncpy(client_id_global, client_id, COMMAND_LENGTH - 1);
client_queue_id_global = client_queue_id;
server_queue_id_global = server_queue_id;
heartbeat_pid = fork();
if (heartbeat_pid == 0) {
heartbeat_loop(server_queue_id, client_id);
return 0;
}
if (fork() == 0) {
input_pid = fork();
if (input_pid == 0) {
input_loop(server_queue_id, client_id);
// The /quit command just returns from the input loop
kill(getppid(), SIGTERM);
return 0;
}
// Setup them below the forks to avoid multiple triggers
signal(SIGINT, cleanup_and_exit);
signal(SIGTERM, cleanup_and_exit);
while (1) {
msgbuf_t incoming;
ssize_t got = msgrcv(client_queue_id, &incoming, sizeof(incoming) - sizeof(long), 0, 0);
@@ -194,7 +236,7 @@ int main(int argc, char *argv[]) {
// Check the command field for the list type
if (strncmp(incoming.command, "active", COMMAND_LENGTH) == 0) {
printf("Active clients:\n%s\n", incoming.message);
} else if (strncmp(incoming.command, "group", COMMAND_LENGTH) == 0) {
} else if (strncmp(incoming.command, "groups", COMMAND_LENGTH) == 0) {
printf("All group members:\n%s\n", incoming.message);
} else {
printf("All clients:\n%s\n", incoming.message);
+27
View File
@@ -0,0 +1,27 @@
// Server configuration file
clients = (
{
client_id = "test1";
nickname = "Kregielnia";
},
{
client_id = "test2";
nickname = "Bajzel";
}
);
groups = (
{
name = "HelloChat";
members = ["test1", "test2"];
},
{
name = "Macarena";
members = [];
},
{
name = "Bananownia";
members = ["test2"];
}
);
+55 -19
View File
@@ -12,7 +12,7 @@
#include <sys/types.h>
#include <unistd.h>
#include "commands.h"
// commands.h already is included by util.h
#include "util/util.h"
#define send(queue_id, msgbuf_ptr) msgsnd(queue_id, msgbuf_ptr, sizeof(*(msgbuf_ptr)) - sizeof(long), 0)
@@ -68,32 +68,57 @@ static void handle_hearbeat_timeouts(Client_t *clients, int client_count, int se
}
int main(int argc, char **argv) {
// TODO: Load config file with clients and groups
int client_count = 2;
int group_count = 3;
server_config_t config;
// If we are given a path, try to load the config from there
if (argc >= 2) {
load_config(&config, argv[1]);
} else {
fprintf(stderr, "No config file provided, using default location example.conf\n");
load_config(&config, "./example.conf");
}
int client_count = config.client_count;
int group_count = config.group_count;
int clients_memory_id = shmget(IPC_PRIVATE, sizeof(Client_t) * client_count, IPC_CREAT | 0666);
Client_t *clients = shmat(clients_memory_id, NULL, 0);
Group_t *groups = malloc(sizeof(Group_t) * 3);
Group_t *groups = malloc(sizeof(Group_t) * group_count);
int semaphore_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
semctl(semaphore_id, 0, SETVAL, 1);
clients[0] =
(Client_t){.id = "test1", .logged_in = false, .muted_clients = NULL, .nickname = "Kregielnia", .last_seen = 0};
clients[1] =
(Client_t){.id = "test2", .logged_in = false, .muted_clients = NULL, .nickname = "Bajzel", .last_seen = 0};
for (int i = 0; i < client_count; i++) {
strncpy(clients[i].id, config.clients[i].client_id, COMMAND_LENGTH - 1);
clients[i].logged_in = false;
clients[i].muted_clients = NULL;
strncpy(clients[i].nickname, config.clients[i].nickname, COMMAND_LENGTH - 1);
clients[i].queue_id = -1;
clients[i].last_seen = 0;
clients[i].saved_message_count = 0;
}
groups[0] = (Group_t){.name = "HelloChat", .client_count = 2, .members = NULL};
groups[0].members = malloc(sizeof(Client_t *) * client_count);
groups[0].members[0] = &clients[0];
groups[0].members[1] = &clients[1];
for (int i = 0; i < group_count; i++) {
strncpy(groups[i].name, config.groups[i].name, COMMAND_LENGTH - 2);
groups[i].client_count = config.groups[i].member_count;
groups[i].members = malloc(sizeof(Client_t *) * config.groups[i].member_count);
for (int j = 0; j < config.groups[i].member_count; j++) {
// Find the client with the given id
for (int k = 0; k < client_count; k++) {
if (strncmp(clients[k].id, config.groups[i].member_ids[j], COMMAND_LENGTH) == 0) {
groups[i].members[j] = &clients[k];
break;
}
}
}
}
groups[1] = (Group_t){.name = "Macarena", .client_count = 0, .members = NULL};
groups[1].members = malloc(sizeof(Client_t *) * client_count);
groups[2] = (Group_t){.name = "Bananownia", .client_count = 1, .members = NULL};
groups[2].members = malloc(sizeof(Client_t *) * client_count);
groups[2].members[0] = &clients[1];
// No longer useful after copying the data over
for (int i = 0; i < group_count; i++) {
for (int j = 0; j < config.groups[i].member_count; j++) {
free(config.groups[i].member_ids[j]);
}
}
free(config.clients);
free(config.groups);
int server_queue_id = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
@@ -119,6 +144,8 @@ int main(int argc, char **argv) {
return 0;
}
printf("Server ready.\n");
msgbuf_t msgbuf;
while (1) {
// Read from the ipc, then handle the command
@@ -305,6 +332,15 @@ int main(int argc, char **argv) {
break;
}
case Logout: {
for (int i = 0; i < client_count; i++) {
if (strncmp(clients[i].id, msgbuf.sender, COMMAND_LENGTH) == 0 && clients[i].logged_in) {
clients[i].logged_in = false;
clients[i].queue_id = -1;
printf("Client %s logged out\n", msgbuf.sender);
break;
}
}
break;
}
case Hearbeat:
for (int i = 0; i < client_count; i++) {
+97
View File
@@ -1,5 +1,12 @@
#include "fcntl.h"
#include "unistd.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libconfig.h>
#include "util.h"
static char alphs[] = {'-', '_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
@@ -22,3 +29,93 @@ void get_id(char *id_buf) {
}
id_buf[15] = '\0';
}
// Load configuration using libconfig
void load_config(server_config_t *cfg, const char *path) {
config_t config;
config_init(&config);
if (!config_read_file(&config, path)) {
fprintf(stderr, "Error reading config file %s:%d - %s\n",
config_error_file(&config),
config_error_line(&config),
config_error_text(&config));
config_destroy(&config);
exit(1);
}
// Read clients
config_setting_t *clients = config_lookup(&config, "clients");
if (!clients) {
fprintf(stderr, "No 'clients' section found in config file\n");
config_destroy(&config);
exit(1);
}
int client_count = config_setting_length(clients);
cfg->client_count = client_count;
cfg->clients = malloc(sizeof(client_config_t) * client_count);
for (int i = 0; i < client_count; i++) {
config_setting_t *client = config_setting_get_elem(clients, i);
const char *client_id, *nickname;
if (!config_setting_lookup_string(client, "client_id", &client_id) ||
!config_setting_lookup_string(client, "nickname", &nickname)) {
fprintf(stderr, "Error reading client %d configuration\n", i);
config_destroy(&config);
exit(1);
}
strncpy(cfg->clients[i].client_id, client_id, COMMAND_LENGTH - 1);
cfg->clients[i].client_id[COMMAND_LENGTH - 1] = '\0';
strncpy(cfg->clients[i].nickname, nickname, COMMAND_LENGTH - 1);
cfg->clients[i].nickname[COMMAND_LENGTH - 1] = '\0';
}
// Read groups
config_setting_t *groups = config_lookup(&config, "groups");
if (!groups) {
fprintf(stderr, "No 'groups' section found in config file\n");
config_destroy(&config);
exit(1);
}
int group_count = config_setting_length(groups);
cfg->group_count = group_count;
cfg->groups = malloc(sizeof(group_config_t) * group_count);
for (int i = 0; i < group_count; i++) {
config_setting_t *group = config_setting_get_elem(groups, i);
const char *name;
config_setting_t *members;
if (!config_setting_lookup_string(group, "name", &name)) {
fprintf(stderr, "Error reading group %d name\n", i);
config_destroy(&config);
exit(1);
}
strncpy(cfg->groups[i].name, name, COMMAND_LENGTH - 2);
cfg->groups[i].name[COMMAND_LENGTH - 2] = '\0';
members = config_setting_get_member(group, "members");
if (!members) {
fprintf(stderr, "Error reading group %d members\n", i);
config_destroy(&config);
exit(1);
}
int member_count = config_setting_length(members);
cfg->groups[i].member_count = member_count;
for (int j = 0; j < member_count; j++) {
const char *member_id = config_setting_get_string_elem(members, j);
cfg->groups[i].member_ids[j] = malloc(COMMAND_LENGTH);
strncpy(cfg->groups[i].member_ids[j], member_id, COMMAND_LENGTH - 1);
cfg->groups[i].member_ids[j][COMMAND_LENGTH - 1] = '\0';
}
}
config_destroy(&config);
}
+22
View File
@@ -1 +1,23 @@
#include "../commands.h"
void get_id(char *id_buf);
typedef struct {
char client_id[COMMAND_LENGTH];
char nickname[COMMAND_LENGTH];
} client_config_t;
typedef struct {
char name[COMMAND_LENGTH - 1];
char *member_ids[COMMAND_LENGTH]; // Array of client IDs
int member_count;
} group_config_t;
typedef struct {
int client_count;
int group_count;
client_config_t *clients;
group_config_t *groups;
} server_config_t;
void load_config(server_config_t *config, const char *path);