From 1188ab5090c2e97c65b065b4d8133efa94e25073 Mon Sep 17 00:00:00 2001
From: Christopher Bohn <bohn@unl.edu>
Date: Tue, 13 Jul 2021 15:46:47 -0500
Subject: [PATCH] Added thread-based chat server

---
 CMakeLists.txt          |   5 +-
 chat-client.c           |   7 +--
 chat-server-iterative.c |   2 +-
 chat-server-processes.c |   2 +-
 chat-server-threads.c   | 125 ++++++++++++++++++++++++++++++++++++++++
 chat-server.c           |  30 ++++++----
 chat.c                  |   5 +-
 chat.h                  |   2 +
 8 files changed, 158 insertions(+), 20 deletions(-)
 create mode 100644 chat-server-threads.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 155777a..6ea93ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,6 +5,7 @@ set(CMAKE_C_STANDARD 99)
 set(CMAKE_C_FLAGS "-lncurses -lpthread")
 
 add_executable(client chat.c chat-client.c)
-add_executable(server chat.c chat-server.c chat-server-processes.c)
+add_executable(server chat.c chat-server.c chat-server-threads.c)
 add_executable(server-iterative chat.c chat-server.c chat-server-iterative.c)
-add_executable(server-processes chat.c chat-server.c chat-server-processes.c)
\ No newline at end of file
+add_executable(server-processes chat.c chat-server.c chat-server-processes.c)
+add_executable(server-threads chat.c chat-server.c chat-server-threads.c)
\ No newline at end of file
diff --git a/chat-client.c b/chat-client.c
index 876ad30..55868ce 100644
--- a/chat-client.c
+++ b/chat-client.c
@@ -15,16 +15,13 @@ void initialize_curses(struct io_windows *windows);
 void initialize_threads(struct io_threads *threads, struct io_windows *windows);
 void *handle_inputs(void *window_arg);
 void *handle_outputs(void *window_arg);
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
-void on_signal();
-#pragma clang diagnostic pop
+void on_signal(int sig);
 void cleanup();
 
-bool running = true;
 int socket_fd;
 pthread_mutex_t mutex;
 int initial_cursor_state;
+bool running = true;
 
 int main() {
     struct io_windows windows;
diff --git a/chat-server-iterative.c b/chat-server-iterative.c
index df4de57..a47814a 100644
--- a/chat-server-iterative.c
+++ b/chat-server-iterative.c
@@ -5,10 +5,10 @@ int accept_connection(int listening_socket_fd);
 void manage_chat(int *socket_fd, char **client_names, int number_of_clients);
 void on_signal(int sig);
 
-bool running = true;
 int client_count;
 int *client_socket_fd;
 bool *disconnected;
+bool running = true;
 
 int main(int argc, const char **argv) {
     if (argc < 2) {
diff --git a/chat-server-processes.c b/chat-server-processes.c
index 4fdb89d..e0a00f3 100644
--- a/chat-server-processes.c
+++ b/chat-server-processes.c
@@ -9,7 +9,6 @@ void on_signal_listener(int sig);
 void on_signal_handler(int sig);
 void on_sigusr1(int sig);
 
-bool running = true;
 int pipe_to_repeater[2];    // write messages from clients to [1] -- repeater will read from [0]
 int listening_socket_fd;
 int accepted_socket_fd;
@@ -17,6 +16,7 @@ int *client_socket_fd_list;
 pid_t *client_handler_pid_list;
 int client_list_length;
 char name[BUFFER_SIZE];
+bool running = true;
 
 int main(int argc, const char **argv) {
     if (argc != 2) {
diff --git a/chat-server-threads.c b/chat-server-threads.c
new file mode 100644
index 0000000..d2769bc
--- /dev/null
+++ b/chat-server-threads.c
@@ -0,0 +1,125 @@
+#include <pthread.h>
+#include "chat-server.h"
+#include "chat.h"
+
+void manage_server(int connect_socket_fd);
+void *handle_client(void *args);
+void on_signal(int sig);
+void repeat_message(int *client_fd_list, int list_length, char *message);
+
+int listening_socket_fd;
+int *client_socket_fd_list;
+int client_list_length;
+pthread_mutex_t mutex;
+bool running = true;
+
+int main(int argc, const char **argv) {
+    if (argc != 2) {
+        fprintf(stderr, "Usage: ./server <portnumber>\n");
+        exit(1);
+    }
+    unsigned short port = (unsigned short)strtol(argv[1], NULL, 10);
+    struct sockaddr_in server_socket_address;
+    listening_socket_fd = listen_to_port(port, &server_socket_address);
+    sigset(SIGINT, on_signal);
+    sigset(SIGABRT, on_signal);
+    sigset(SIGKILL, on_signal);
+    sigset(SIGSEGV, on_signal);
+    sigset(SIGTERM, on_signal);
+    display_host_info(port);
+    pthread_mutex_init(&mutex, NULL);
+    manage_server(listening_socket_fd);
+}
+
+void manage_server(int connect_socket_fd) {
+    struct sockaddr_in client_address;
+    unsigned int client_address_length = sizeof(client_address);
+    client_socket_fd_list = calloc(CLIENT_QUEUE_DEPTH, sizeof(int));       // initial size
+    client_list_length = CLIENT_QUEUE_DEPTH;
+    char buffer[BUFFER_SIZE];
+    bzero(buffer, BUFFER_SIZE);
+    pthread_t tid;
+    while (running) {
+        bzero((char *)&client_address, sizeof(struct sockaddr_in));
+        int *accepted_socket_fd = malloc(sizeof(int));      // so it's not on the original thread's stack
+        *accepted_socket_fd = accept(connect_socket_fd, (struct sockaddr *)&client_address, &client_address_length);
+        pthread_create(&tid, NULL, handle_client, accepted_socket_fd);
+    }
+}
+
+void *handle_client(void *args) {
+    int socket_fd = *((int *)args);                         // so it's on this thread's stack
+    free(args);
+    pthread_detach(pthread_self());                         // so this thread can be reaped when it terminates
+    pthread_mutex_lock(&mutex);
+    add_client(socket_fd, &client_socket_fd_list, 0, NULL, &client_list_length);
+    pthread_mutex_unlock(&mutex);
+    char name[BUFFER_SIZE];
+    char *buffer = malloc(BUFFER_SIZE);
+    buffer = receive_message(socket_fd, buffer);
+    printf("[STATUS]\tAccepted connection from %s.\n", buffer);
+    strcpy(buffer, "[SERVER]\tPlease type your name.");
+    send_message(socket_fd, buffer);
+    buffer = receive_message(socket_fd, buffer);
+    printf("[STATUS]\tClient identified as %s.\n", buffer);
+    strncpy(name, buffer, BUFFER_SIZE);
+    sprintf(buffer, "[SERVER]\tWelcome, %s.", name);
+    send_message(socket_fd, buffer);
+    printf("[STATUS]\tThread %ld is listening for messages from %s.\n", (long)pthread_self(), name);
+    char *message_to_repeat = malloc(BUFFER_SIZE + strlen(name) + 3);
+    sprintf(message_to_repeat, "[SERVER]\t%s has joined the chat.", name);
+    bool me_running = true;
+    while (running && me_running) {
+        pthread_mutex_lock(&mutex);
+        repeat_message(client_socket_fd_list, client_list_length, message_to_repeat);
+        pthread_mutex_unlock(&mutex);
+        buffer = receive_message(socket_fd, buffer);
+        sprintf(message_to_repeat, "[%s]\t%s", name, buffer);
+        if (!strncmp(buffer, "EXIT", 4)) {
+            printf("%s\n", message_to_repeat);
+            pthread_mutex_lock(&mutex);
+            send_message(socket_fd, message_to_repeat);
+            pthread_mutex_unlock(&mutex);
+            sprintf(message_to_repeat, "[SERVER]\t%s has left the chat.", name);
+            me_running = false;
+        }
+    }
+    pthread_mutex_lock(&mutex);
+    remove_client(socket_fd, client_socket_fd_list, NULL);
+    repeat_message(client_socket_fd_list, client_list_length, message_to_repeat);
+    pthread_mutex_unlock(&mutex);
+    close(socket_fd);
+    free(message_to_repeat);
+    free(buffer);
+    return NULL;
+}
+
+void repeat_message(int *client_fd_list, int list_length, char *message) {
+    printf("%s\n", message);
+    for (int i = 0; i < list_length; i++) {
+        if (client_fd_list[i]) {
+            send_message(client_fd_list[i], message);
+        }
+    }
+    bzero(message, BUFFER_SIZE);
+}
+
+void on_signal(int sig) {
+    sigset(SIGINT, SIG_IGN);
+    sigset(SIGABRT, SIG_IGN);
+    sigset(SIGKILL, SIG_IGN);
+    sigset(SIGSEGV, SIG_IGN);
+    sigset(SIGTERM, SIG_IGN);
+    running = false;
+    close(listening_socket_fd);
+    pthread_mutex_lock(&mutex);
+    for(int i=0; i<client_list_length; i++) {
+        if(client_socket_fd_list[i]) {
+            send_message(client_socket_fd_list[i], "[SERVER]\tServer is terminating. EXIT");
+            close(client_socket_fd_list[i]);
+        }
+    }
+    pthread_mutex_unlock(&mutex);
+    pthread_mutex_destroy(&mutex);
+    exit(128 + sig);
+}
\ No newline at end of file
diff --git a/chat-server.c b/chat-server.c
index f4a54d8..290e6f5 100644
--- a/chat-server.c
+++ b/chat-server.c
@@ -1,6 +1,3 @@
-#include "chat.h"
-#include <signal.h>
-#include <netdb.h>
 #include "chat-server.h"
 
 void display_host_info(unsigned short port) {
@@ -56,35 +53,48 @@ int listen_to_port(unsigned short port, struct sockaddr_in *server_socket_addres
 void add_client(int new_client_fd, int **client_fd_list, pid_t new_handler_pid, pid_t **handler_pid_list, int *list_length) {
     int i = 0;
     int *client_list = *client_fd_list;
-    int *handler_list = *handler_pid_list;
+    int *handler_list = NULL;
+    if (handler_pid_list) {
+        handler_list = *handler_pid_list;
+    }
     while (client_list[i] && (i < *list_length)) {
         i++;
     }
     if (i == *list_length) {
         // need to grow the lists
         client_list = calloc(2 * (*list_length), sizeof(int));
-        handler_list = calloc(2 * (*list_length), sizeof(pid_t));
+        if (handler_list) {
+            handler_list = calloc(2 * (*list_length), sizeof(pid_t));
+        }
         for (int j = 0; j < *list_length; j++) {
             client_list[j] = (*client_fd_list)[j];
-            handler_list[j] = (*handler_pid_list)[j];
+            if (handler_list) {
+                handler_list[j] = (*handler_pid_list)[j];
+            }
         }
         free(*client_fd_list);
         free(*handler_pid_list);
         *list_length *= 2;
     }
     client_list[i] = new_client_fd;
-    handler_list[i] = new_handler_pid;
     *client_fd_list = client_list;
-    *handler_pid_list = handler_list;
+    if (handler_list) {
+        handler_list[i] = new_handler_pid;
+        *handler_pid_list = handler_list;
+    }
 }
 
 void remove_client(int exiting_client_fd, int *client_fd_list, pid_t *handler_pid_list) {
     if (exiting_client_fd) {
         while (*client_fd_list != exiting_client_fd) {
             client_fd_list++;
-            handler_pid_list++;
+            if (handler_pid_list) {
+                handler_pid_list++;
+            }
         }
         *client_fd_list = 0;
-        *handler_pid_list = 0;
+        if (handler_pid_list) {
+            *handler_pid_list = 0;
+        }
     }
 }
\ No newline at end of file
diff --git a/chat.c b/chat.c
index 0ec6683..f67408c 100644
--- a/chat.c
+++ b/chat.c
@@ -1,9 +1,12 @@
+#include <errno.h>
 #include "chat.h"
 
 char *receive_message(int socket_fd, char *message_buffer) {
     long number_of_received_bytes = read(socket_fd, message_buffer, BUFFER_SIZE - 1);
     if (number_of_received_bytes < 0) {
-        perror("Error while receiving message");
+        if (!(errno == EBADF && !running)) {
+            perror("Error while receiving message");
+        }
         number_of_received_bytes = 0;
     }
     message_buffer[number_of_received_bytes] = '\0';
diff --git a/chat.h b/chat.h
index fc69cf1..36a57d6 100644
--- a/chat.h
+++ b/chat.h
@@ -12,6 +12,8 @@
 
 #define BUFFER_SIZE 256
 
+extern bool running;
+
 char *receive_message(int socket_fd, char *message_buffer);
 void send_message(int socket_fd, char *message);
 
-- 
GitLab