Server config load and graceful client exit
This commit is contained in:
@@ -1 +1,2 @@
|
||||
build
|
||||
.conf
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
);
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user