From e93746054bf38e1b73f308fabd503c76b977c0d5 Mon Sep 17 00:00:00 2001 From: SasikaSankalana <79664299+SasikaSankalana@users.noreply.github.com> Date: Sat, 30 Sep 2023 23:23:32 +0530 Subject: [PATCH] Add Chat room using C --- c/Chat-room/README.md | 23 +++ c/Chat-room/client.c | 389 +++++++++++++++++++++++++++++++++++ c/Chat-room/server.c | 466 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 878 insertions(+) create mode 100644 c/Chat-room/README.md create mode 100644 c/Chat-room/client.c create mode 100644 c/Chat-room/server.c diff --git a/c/Chat-room/README.md b/c/Chat-room/README.md new file mode 100644 index 0000000..6ca9641 --- /dev/null +++ b/c/Chat-room/README.md @@ -0,0 +1,23 @@ +# Chat-room +A Simple Network Chat (SNC) built in C programming language. The program has two files - server.c and client.c. The program uses multithreading for handling multiple clients. + +# Compile the program +Just simply run the Makefile using this command.
+$ make Makefile compile + +# Run the Server +The server can be run using below command.
+$ ./server + +Maximum number of clients - Should be in range of 1 to 10 +Maximum idle time - Should be in range of 1 to 300 seconds + +eg:- $ ./server 5 200 + +# Run the Client +The client can be run using below command.
+$ ./client + +In here server IP is set to be - 127.0.0.1 and port is set to be 4444 + +eg:- $ ./client 127.0.0.1 4444 diff --git a/c/Chat-room/client.c b/c/Chat-room/client.c new file mode 100644 index 0000000..ecb04aa --- /dev/null +++ b/c/Chat-room/client.c @@ -0,0 +1,389 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LENGTH 256 +#define ID "123" + +// Global variables +volatile sig_atomic_t flag = 0; +int sockfd = 0; +char input_text[LENGTH + 100]; + +struct snc_command_holder +{ + char command[50]; + char sub_command[50]; + char sub_text[LENGTH]; +} snc_command; + +char upper_text[50]; + +char name[20]; + +void str_overwrite_stdout() +{ + printf("%s > ", ID); + fflush(stdout); +} + +void SNC_command_retriever() +{ + + snc_command.command[0] = 0; + snc_command.sub_command[0] = 0; + snc_command.sub_text[0] = 0; + str_overwrite_stdout(); + fgets(input_text, LENGTH, stdin); + str_trim_lf(input_text, LENGTH); + sscanf(input_text, "%s %s %[^\n]", snc_command.command, snc_command.sub_command, snc_command.sub_text); + toUpper(snc_command.command); + strcpy(snc_command.command, upper_text); +} + +int SNC_command_validator() +{ + if (strcmp(snc_command.command, "JOIN") == 0) + { + + + if (strlen(snc_command.sub_command) > 20) + { + printf("Only 20 characters allowed for the nick name \n "); + return 0; + } + + if (!(strlen(snc_command.sub_command) > 0)) + { + printf("Nickname cannot be empty \n "); + return 0; + } + + if (strlen(snc_command.sub_text) > 20) + { + printf("Only 20 characters allowed for the name \n "); + return 0; + } + + if (!(strlen(snc_command.sub_text) > 0)) + { + printf("Name cannot be empty \n "); + return 0; + } + + return 1; + } + else if (strcmp(snc_command.command, "WHOIS") == 0) + { + if (strlen(snc_command.sub_command) > 20) + { + printf("Nickname should be under 20 characters \n "); + return 0; + } + + if (!(strlen(snc_command.sub_command) > 0)) + { + printf("Enter a nickname \n "); + return 0; + } + + if ((strlen(snc_command.sub_text) > 0)) + { + printf("Invalid argument. Please check again \n "); + return 0; + } + + return 1; + } + else if (strcmp(snc_command.command, "MSG") == 0) + { + if (strlen(snc_command.sub_command) > 20) + { + printf("Nickname should be under 20 characters \n "); + return 0; + } + + if (!(strlen(snc_command.sub_command) > 0)) + { + printf("Enter a nickname \n "); + return 0; + } + + if (strlen(snc_command.sub_text) > 250) + { + printf("Only 256 characters allowed in a message \n "); + return 0; + } + + if (!(strlen(snc_command.sub_text) > 0)) + { + printf("Message cannot be empty \n "); + return 0; + } + + return 1; + } + else if (strcmp(snc_command.command, "TIME") == 0) + { + if ((strlen(snc_command.sub_command) > 0) || (strlen(snc_command.sub_text) > 0)) + { + printf("Invalid argument. Please check again \n "); + return 0; + } + + return 1; + } + else if (strcmp(snc_command.command, "ALIVE") == 0) + { + if ((strlen(snc_command.sub_command) > 0) || (strlen(snc_command.sub_text) > 0)) + { + printf("Invalid argument. Please check again \n "); + return 0; + } + + return 1; + } + else if (strcmp(snc_command.command, "QUIT") == 0) + { + if ((strlen(snc_command.sub_command) > 0) || (strlen(snc_command.sub_text) > 0)) + { + printf("Invalid argument. Please check again \n "); + return 0; + } + return 1; + } + else + { + // printf("%s",snc_command.command); + printf("Invalid Command. Please check again \n "); + return 0; + } +} + +void str_trim_lf(char *arr, int length) +{ + int i; + for (i = 0; i < length; i++) + { // trim \n + if (arr[i] == '\n') + { + arr[i] = '\0'; + break; + } + } +} + +void catch_ctrl_c_and_exit(int sig) +{ + flag = 1; +} + +void send_msg_handler() +{ + // char message[LENGTH] = {}; + char buffer[LENGTH + 32] = {}; + + while (1) + { + SNC_command_retriever(); + if (SNC_command_validator() == 0) + { + continue; + } + + // str_overwrite_stdout(); + // fgets(message, LENGTH, stdin); + // str_trim_lf(message, LENGTH); + + if (strcmp(snc_command.command, "QUIT") == 0) + { + break; + } + else + { + + + send(sockfd, buffer, strlen(buffer), 0); + } + + // bzero(message, LENGTH); + bzero(buffer, LENGTH + 32); + } + catch_ctrl_c_and_exit(2); +} + +void recv_msg_handler() +{ + char message[LENGTH] = {}; + while (1) + { + int receive = recv(sockfd, message, LENGTH, 0); + if (receive > 0) + { + printf("%s", message); + str_overwrite_stdout(); + } + else if (receive == 0) + { + break; + } + else + { + // -1 + } + memset(message, 0, sizeof(message)); + } +} + +void toUpper(char *text) +{ + + for (int i = 0; i < 51; i++) + { + upper_text[i] = '\0'; + } + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] >= 'a' && text[i] <= 'z') + { + upper_text[i] = text[i] - 32; + } + } +} + +int main(int argc, char **argv) +{ + if (argc != 3) + { + printf("Insufficient parameters supplied to the command.\n"); + return EXIT_FAILURE; + } + + char *ip = argv[1]; + int port = atoi(argv[2]); + + signal(SIGINT, catch_ctrl_c_and_exit); + +retrieve_again: + SNC_command_retriever(); + + if (SNC_command_validator == 0) + { + goto retrieve_again; + } + + if (strcmp(snc_command.command, "JOIN") == 0) + { + + strcpy(name, snc_command.sub_command); + // str_trim_lf(name, strlen(name)); + + struct sockaddr_in server_address; + + /* Socket settings */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = inet_addr(ip); + server_address.sin_port = htons(port); + + // Connect to Server + int connect_error = connect(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)); + if (connect_error == -1) + { + printf("ERROR: connect\n"); + return EXIT_FAILURE; + } + char name_buffer[50]; + + sprintf(name_buffer, "%s %s", name, snc_command.sub_text); + + // Send name + send(sockfd, name_buffer, 50, 0); + + + //printf("=== WELCOME TO THE CHATROOM ===\n"); + + pthread_t send_msg_thread; + if (pthread_create(&send_msg_thread, NULL, (void *)send_msg_handler, NULL) != 0) + { + printf("ERROR: pthread\n"); + return EXIT_FAILURE; + } + + pthread_t receive_msg_thread; + if (pthread_create(&receive_msg_thread, NULL, (void *)recv_msg_handler, NULL) != 0) + { + printf("ERROR: pthread\n"); + return EXIT_FAILURE; + } + + } + + // // printf("Current Time : %s\n", time_str); + // } + // else if (strcmp(upText, "ALIVE") == 0) + // { + // // do something else + // } + // else if (strcmp(upText, "QUIT") == 0) + // { + // is_joined = 0; + // } + else /* default: */ + { + + printf("Please Join the Server\n"); + goto retrieve_again; + } + + // printf("Please enter your name: "); + // fgets(name, 32, stdin); + // str_trim_lf(name, strlen(name)); + + // if (strlen(name) > 32 || strlen(name) < 2) + // { + // printf("Name must be less than 30 and more than 2 characters.\n"); + // return EXIT_FAILURE; + // } + + // struct sockaddr_in server_address; + + // /* Socket settings */ + // sockfd = socket(AF_INET, SOCK_STREAM, 0); + // server_address.sin_family = AF_INET; + // server_address.sin_addr.s_addr = inet_addr(ip); + // server_address.sin_port = htons(port); + + // // Connect to Server + // int connect_error = connect(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)); + // if (connect_error == -1) + // { + // printf("ERROR: connect\n"); + // return EXIT_FAILURE; + // } + + // // Send name + // send(sockfd, name, 32, 0); + + // printf("=== WELCOME TO THE CHATROOM ===\n"); + + while (1) + { + if (flag) + { + printf("\nBye\n"); + break; + } + } + + close(sockfd); + + return EXIT_SUCCESS; +} diff --git a/c/Chat-room/server.c b/c/Chat-room/server.c new file mode 100644 index 0000000..e6dbb2f --- /dev/null +++ b/c/Chat-room/server.c @@ -0,0 +1,466 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SZ 288 +#define MAX_CLIENTS 10 + +static _Atomic unsigned int client_count = 0; +static int uid = 10; +char upper_text[50]; +int user_check = 0; +int max_no_of_clients; + +/* Client structure */ +typedef struct +{ + struct sockaddr_in address; + int sockfd; + int uid; + char fullname[20]; + char name[20]; +} client_t; + +struct snc_command_holder +{ + char nick_name[20]; + char command[50]; + char sub_command[50]; + char sub_text[256]; +} snc_command; + +client_t *clients[MAX_CLIENTS]; + +pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; + +void toUpper(char *text) +{ + + for (int i = 0; i < 51; i++) + { + upper_text[i] = '\0'; + } + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] >= 'a' && text[i] <= 'z') + { + upper_text[i] = text[i] - 32; + } + } +} + +void str_overwrite_stdout() +{ + printf("\r%s", "> "); + fflush(stdout); +} + +void str_trim_lf(char *arr, int length) +{ + int i; + for (i = 0; i < length; i++) + { // trim \n + if (arr[i] == '\n') + { + arr[i] = '\0'; + break; + } + } +} + +void print_client_addr(struct sockaddr_in addr) +{ + printf("%d.%d.%d.%d", + addr.sin_addr.s_addr & 0xff, + (addr.sin_addr.s_addr & 0xff00) >> 8, + (addr.sin_addr.s_addr & 0xff0000) >> 16, + (addr.sin_addr.s_addr & 0xff000000) >> 24); +} + +/* Add clients to queue */ +void queue_add(client_t *cl) +{ + pthread_mutex_lock(&clients_mutex); + + for (int i = 0; i < max_no_of_clients; ++i) + { + // printf("%d\t%d\t%s\t%s", clients[i]->sockfd, clients[i]->uid, clients[i]->name, clients[i]->fullname); + if (!clients[i]) + { + clients[i] = cl; + break; + } + } + + pthread_mutex_unlock(&clients_mutex); +} + +/* Remove clients to queue */ +void queue_remove(int uid) +{ + pthread_mutex_lock(&clients_mutex); + + for (int i = 0; i < max_no_of_clients; ++i) + { + if (clients[i]) + { + if (clients[i]->uid == uid) + { + clients[i] = NULL; + break; + } + } + } + + pthread_mutex_unlock(&clients_mutex); +} + +/* Send message to all clients except sender */ +void broadcast(char *s, int uid) +{ + pthread_mutex_lock(&clients_mutex); + + for (int i = 0; i < max_no_of_clients; ++i) + { + if (clients[i]) + { + if (clients[i]->uid != uid) + { + if (write(clients[i]->sockfd, s, strlen(s)) < 0) + { + perror("ERROR: write to descriptor failed"); + break; + } + } + } + } + + pthread_mutex_unlock(&clients_mutex); +} + +/* Send message to target */ +void send_message(char *s, int uid) +{ + pthread_mutex_lock(&clients_mutex); + + for (int i = 0; i < max_no_of_clients; ++i) + { + if (clients[i]) + { + if (clients[i]->uid == uid) + { + if (write(clients[i]->sockfd, s, strlen(s)) < 0) + { + perror("ERROR: write to descriptor failed"); + break; + } + } + } + } + + pthread_mutex_unlock(&clients_mutex); +} + +char *get_time() +{ + time_t current_time = time(NULL); + char *time_str = ctime(¤t_time); + time_str[strlen(time_str) - 1] = '\0'; + + return time_str; +} + +/* Handle all communication with the client */ +void *handle_client(void *arg) +{ + char buff_out[BUFFER_SZ]; + char name[50]; + char fullname[20]; + char nickname[20]; + int leave_flag = 0; + + client_count++; + client_t *cli = (client_t *)arg; + + // Name + if (recv(cli->sockfd, name, 50, 0) <= 0 || strlen(name) <= 0) + { + printf("Didn't enter the name.\n"); + leave_flag = 1; + } + else + { + sscanf(name, "%s %[^\n]", nickname, fullname); + + for (int i = 0; i < MAX_CLIENTS; ++i) + { + if (clients[i]) + { + if (strcmp(clients[i]->fullname, fullname) == 0) + { + sprintf(buff_out, "SERVER : NAME ALREADY EXIST.\n"); + send_message(buff_out, cli->uid); + leave_flag = 1; + break; + } else if (strcmp(clients[i]->name, nickname) == 0) + { + sprintf(buff_out, "SERVER : NICKNAME ALREADY TAKEN. CHOOSE A DIFFERENT NICKNAME AND JOIN.\n"); + send_message(buff_out, cli->uid); + leave_flag = 1; + break; + } + } + } + + if(!leave_flag) { + strcpy(cli->name, nickname); + strcpy(cli->fullname, fullname); + sprintf(buff_out, "%s has joined\n", cli->name); + + broadcast(buff_out, cli->uid); + user_check = 1; + } + } + + bzero(buff_out, BUFFER_SZ); + + while (1) + { + if (leave_flag) + { + break; + } + + int receive = recv(cli->sockfd, buff_out, BUFFER_SZ, 0); + if (receive >= 0) + { + printf("check 1\n"); + + sscanf(buff_out, "%s %s %s %[^\n]", snc_command.nick_name, snc_command.command, snc_command.sub_command, snc_command.sub_text); + // bzero(buff_out, BUFFER_SZ); + toUpper(snc_command.command); + } + + if (receive > 0) + { + + if (strcmp(upper_text, "JOIN") == 0 ){ + + if(!user_check){ + sscanf(name, "%s %[^\n]", nickname, fullname); + + for (int i = 0; i < MAX_CLIENTS; ++i) + { + if (clients[i]) + { + if (strcmp(clients[i]->fullname, fullname) == 0) + { + sprintf(buff_out, "SERVER : NAME ALREADY EXIST.\n"); + send_message(buff_out, cli->uid); + leave_flag = 1; + break; + } else if (strcmp(clients[i]->name, nickname) == 0) + { + sprintf(buff_out, "SERVER : NICKNAME ALREADY TAKEN. CHOOSE A DIFFERENT NICKNAME AND JOIN.\n"); + send_message(buff_out, cli->uid); + leave_flag = 1; + break; + } + } + }}else{ + + sprintf(buff_out, "SERVER : ALREADY JOINED\n"); + send_message(buff_out, cli->uid); + } + + + if(!leave_flag) { + strcpy(cli->name, nickname); + strcpy(cli->fullname, fullname); + sprintf(buff_out, "%s has joined\n", cli->name); + + broadcast(buff_out, cli->uid); + user_check = 1; + } + + } + else if (strcmp(upper_text, "MSG") == 0) + { + int check = 0; + for (int i = 0; i < MAX_CLIENTS; ++i) + { + if (clients[i]) + { + if (strcmp(snc_command.sub_command, clients[i]->name) == 0) + { + sprintf(buff_out, "%s : %s\n", cli->name, snc_command.sub_text); + send_message(buff_out, clients[i]->uid); + check = 1; + break; + } + } + } + if (!check) + { + printf("CLIENT \"%s\" NOT REGISTERED\n",snc_command.sub_command); + sprintf(buff_out, "SERVER : CHECK THE NICKNAME AGAIN\n"); + send_message(buff_out, cli->uid); + } + } + + else if (strcmp(upper_text, "WHOIS") == 0) + { + int check = 0; + for (int i = 0; i < MAX_CLIENTS; ++i) + { + if (clients[i]) + { + if (strcmp(snc_command.sub_command, clients[i]->name) == 0) + { + sprintf(buff_out, "SERVER : NICKNAME -> %s FULL NAME -> \n", clients[i]->name, clients[i]->fullname); + send_message(buff_out, cli->uid); + check = 1; + break; + } + } + } + if (!check) + { + printf("CLIENT \"%s\" NOT REGISTERED\n",snc_command.sub_command); + sprintf(buff_out, "SERVER : CHECK THE NICKNAME AGAIN\n"); + send_message(buff_out, cli->uid); + } + } else if (strcmp(upper_text, "TIME") == 0) + { + send_message(get_time(), cli->uid); + } + } + else if (receive == 0 || strcmp(buff_out, "QUIT") == 0) + { + sprintf(buff_out, "%s has left\n", cli->name); + printf("%s", buff_out); + broadcast(buff_out, cli->uid); + leave_flag = 1; + } + else + { + printf("ERROR: -1\n"); + leave_flag = 1; + } + + bzero(buff_out, BUFFER_SZ); + } + + /* Delete client from queue and yield thread */ + close(cli->sockfd); + queue_remove(cli->uid); + free(cli); + client_count--; + pthread_detach(pthread_self()); + + return NULL; +} + +int main(int argc, char **argv) +{ + if (argc != 3) + { + printf("Insufficient parameters supplied to the command.\n"); + return EXIT_FAILURE; + } + + int max_idle_time = atoi(argv[2]); + max_no_of_clients = atoi(argv[1]); + + if (max_idle_time > 100 || max_idle_time < 1) + { + printf("Invalid parameters supplied to the command\n"); + return EXIT_FAILURE; + } + + if (max_no_of_clients > 10 || max_no_of_clients < 1) + { + printf("Invalid parameters supplied to the command\n"); + return EXIT_FAILURE; + } + + char *ip = "127.0.0.1"; + int port = 4444; + + int option = 1; + int listenfd = 0, connfd = 0; + struct sockaddr_in server_address; + struct sockaddr_in client_address; + pthread_t thread_id; + + /* Socket settings */ + listenfd = socket(AF_INET, SOCK_STREAM, 0); + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = inet_addr(ip); + server_address.sin_port = htons(port); + + /* Ignore pipe signals */ + signal(SIGPIPE, SIG_IGN); + + if (setsockopt(listenfd, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR), (char *)&option, sizeof(option)) < 0) + { + perror("ERROR: setsockopt failed"); + return EXIT_FAILURE; + } + + /* Bind */ + if (bind(listenfd, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) + { + perror("ERROR: Socket binding failed"); + return EXIT_FAILURE; + } + + /* Listen */ + if (listen(listenfd, 10) < 0) + { + perror("ERROR: Socket listening failed"); + return EXIT_FAILURE; + } + + printf("=== WELCOME TO THE CHATROOM ===\n"); + + while (1) + { + socklen_t client_length = sizeof(client_address); + connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_length); + + /* Check if max clients is reached */ + if ((client_count + 1) == max_no_of_clients) + { + printf("Max clients reached. Rejected: "); + print_client_addr(client_address); + printf(":%d\n", client_address.sin_port); + close(connfd); + continue; + } + + /* Client settings */ + client_t *cli = (client_t *)malloc(sizeof(client_t)); + cli->address = client_address; + cli->sockfd = connfd; + cli->uid = uid++; + + /* Add client to the queue and fork thread */ + queue_add(cli); + user_check = 0; + pthread_create(&thread_id, NULL, &handle_client, (void *)cli); + + /* Reduce CPU usage */ + sleep(1); + } + + return EXIT_SUCCESS; +}