From 9a37efceb87e01923f75eddcfd0c69b027ce7823 Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 2 Aug 2025 15:52:43 +0530 Subject: [PATCH 1/6] Add files via upload Split it into logical modules (commands + shared server_types). Added five real slash-commands for listing users, renaming, private messaging, color changes, and help. Cleaned up a few minor C-API mishaps (headers, redundant zeroing). Ensured all shared functions / globals use a single, matching declaration in server_types.h. --- client.cpp | 206 ++++++++++++++------------------ commands.cpp | 131 ++++++++++++++++++++ commands.h | 14 +++ server.cpp | 318 +++++++++++++++++++++++-------------------------- server_types.h | 35 ++++++ 5 files changed, 424 insertions(+), 280 deletions(-) create mode 100644 commands.cpp create mode 100644 commands.h create mode 100644 server_types.h diff --git a/client.cpp b/client.cpp index 2928f87..11537ca 100644 --- a/client.cpp +++ b/client.cpp @@ -1,139 +1,117 @@ +// client.cpp #include #include #include #include -#include -#include #include #include #include #include -#include #define MAX_LEN 200 #define NUM_COLORS 6 using namespace std; -bool exit_flag=false; +bool exit_flag = false; thread t_send, t_recv; int client_socket; -string def_col="\033[0m"; -string colors[]={"\033[31m", "\033[32m", "\033[33m", "\033[34m", "\033[35m", "\033[36m"}; - -void catch_ctrl_c(int signal); -string color(int code); -int eraseText(int cnt); -void send_message(int client_socket); -void recv_message(int client_socket); - -int main() -{ - if((client_socket=socket(AF_INET,SOCK_STREAM,0))==-1) - { - perror("socket: "); - exit(-1); - } - - struct sockaddr_in client; - client.sin_family=AF_INET; - client.sin_port=htons(10000); // Port no. of server - client.sin_addr.s_addr=INADDR_ANY; - //client.sin_addr.s_addr=inet_addr("127.0.0.1"); // Provide IP address of server - bzero(&client.sin_zero,0); - - if((connect(client_socket,(struct sockaddr *)&client,sizeof(struct sockaddr_in)))==-1) - { - perror("connect: "); - exit(-1); - } - signal(SIGINT, catch_ctrl_c); - char name[MAX_LEN]; - cout<<"Enter your name : "; - cin.getline(name,MAX_LEN); - send(client_socket,name,sizeof(name),0); - - cout< +#include +#include +#include +#include // for send() +#include + +static const int SYS_COLOR = NUM_COLORS - 1; + +void Commands_Init() { + // no-op +} + +static void reply_sys(int sock, const std::string& text) { + char buf[MAX_LEN] = {0}; + std::strncpy(buf, text.c_str(), MAX_LEN - 1); + send(sock, "#NULL", MAX_LEN, 0); + send(sock, &SYS_COLOR, sizeof(SYS_COLOR), 0); + send(sock, buf, MAX_LEN, 0); +} + +bool Commands_Handle(const std::string& input, int sender_id) { + int idx = get_client_index(sender_id); + if (idx < 0) return false; + int sock = clients[idx].socket; + + if (input == "/help") { + std::string h = + "Available commands:\n" + " /help - Show this list\n" + " /users - List online users\n" + " /rename - Change your name\n" + " /msg - Private message\n" + " /color <0-5> - Change your color\n"; + reply_sys(sock, h); + return true; + } + + if (input == "/users") { + std::ostringstream oss; + oss << "Online users:\n"; + { + std::lock_guard guard(clients_mtx); + for (auto& c : clients) + oss << " - " << c.name << "\n"; + } + reply_sys(sock, oss.str()); + return true; + } + + if (input.rfind("/rename ", 0) == 0) { + std::string new_name = input.substr(8); + if (new_name.empty()) { + reply_sys(sock, "Usage: /rename "); + return true; + } + bool taken = false; + { + std::lock_guard guard(clients_mtx); + for (auto& c : clients) + if (c.name == new_name) { taken = true; break; } + if (!taken) { + std::string old = clients[idx].name; + clients[idx].name = new_name; + std::string note = old + " is now " + new_name; + broadcast_message("#NULL", sender_id); + broadcast_message(SYS_COLOR, sender_id); + broadcast_message(note, sender_id); + shared_print(colors[SYS_COLOR] + note + def_col, true); + reply_sys(sock, "You are now \"" + new_name + "\""); + } + } + if (taken) + reply_sys(sock, "Name \"" + new_name + "\" is already taken."); + return true; + } + + if (input.rfind("/msg ", 0) == 0) { + std::istringstream iss(input.substr(5)); + std::string target, word, body; + iss >> target; + while (iss >> word) body += word + " "; + if (target.empty() || body.empty()) { + reply_sys(sock, "Usage: /msg "); + return true; + } + int tgt_idx = -1; + { + std::lock_guard guard(clients_mtx); + for (int i = 0; i < (int)clients.size(); i++) + if (clients[i].name == target) { tgt_idx = i; break; } + } + if (tgt_idx < 0) { + reply_sys(sock, "User \"" + target + "\" not found."); + } else { + char nb[MAX_LEN] = {0}, mb[MAX_LEN] = {0}; + std::strncpy(nb, clients[idx].name.c_str(), MAX_LEN - 1); + std::string pref = "(Private) " + body; + std::strncpy(mb, pref.c_str(), MAX_LEN - 1); + int col = clients[idx].color_code; + + send(clients[tgt_idx].socket, nb, MAX_LEN, 0); + send(clients[tgt_idx].socket, &col, sizeof(col), 0); + send(clients[tgt_idx].socket, mb, MAX_LEN, 0); + + reply_sys(sock, "Sent to " + target + ": " + body); + } + return true; + } + + if (input.rfind("/color ", 0) == 0) { + try { + int c = std::stoi(input.substr(7)); + if (c < 0 || c >= NUM_COLORS) throw std::out_of_range(""); + { + std::lock_guard guard(clients_mtx); + clients[idx].color_code = c; + } + reply_sys(sock, "Color set to code " + std::to_string(c)); + } catch (...) { + reply_sys(sock, "Invalid code; use 0–" + std::to_string(NUM_COLORS - 1)); + } + return true; + } + + return false; +} diff --git a/commands.h b/commands.h new file mode 100644 index 0000000..9962f2d --- /dev/null +++ b/commands.h @@ -0,0 +1,14 @@ +// commands.h +#ifndef COMMANDS_H +#define COMMANDS_H + +#include + +// Call once at server startup +void Commands_Init(); + +// Try to handle a leading-β€˜/’ command from client `sender_id`. +// Returns true if handled (and no further broadcast is needed). +bool Commands_Handle(const std::string& input, int sender_id); + +#endif // COMMANDS_H diff --git a/server.cpp b/server.cpp index e66ec5b..d231a85 100644 --- a/server.cpp +++ b/server.cpp @@ -1,195 +1,181 @@ +// server.cpp #include #include #include #include -#include -#include #include #include #include #include -#define MAX_LEN 200 -#define NUM_COLORS 6 + +#include "commands.h" +#include "server_types.h" using namespace std; -struct terminal -{ - int id; - string name; - int socket; - thread th; +// globals (also declared extern in server_types.h) +const int MAX_LEN = 200; +const int NUM_COLORS = 6; +const string def_col = "\033[0m"; +const string colors[NUM_COLORS] = { + "\033[31m", "\033[32m", "\033[33m", + "\033[34m", "\033[35m", "\033[36m" }; -vector clients; -string def_col="\033[0m"; -string colors[]={"\033[31m", "\033[32m", "\033[33m", "\033[34m", "\033[35m","\033[36m"}; -int seed=0; -mutex cout_mtx,clients_mtx; - -string color(int code); -void set_name(int id, char name[]); -void shared_print(string str, bool endLine); -int broadcast_message(string message, int sender_id); -int broadcast_message(int num, int sender_id); +vector clients; +mutex clients_mtx, cout_mtx; +int seed = 0; + +// forward declarations +int get_client_index(int id); +void shared_print(const string& str, bool endLine=true); +int broadcast_message(const string& message, int sender_id); +int broadcast_message(int num, int sender_id); void end_connection(int id); void handle_client(int client_socket, int id); -int main() -{ - int server_socket; - if((server_socket=socket(AF_INET,SOCK_STREAM,0))==-1) - { - perror("socket: "); - exit(-1); - } - - struct sockaddr_in server; - server.sin_family=AF_INET; - server.sin_port=htons(10000); - server.sin_addr.s_addr=INADDR_ANY; - bzero(&server.sin_zero,0); - - if((bind(server_socket,(struct sockaddr *)&server,sizeof(struct sockaddr_in)))==-1) - { - perror("bind error: "); - exit(-1); - } - - if((listen(server_socket,8))==-1) - { - perror("listen error: "); - exit(-1); - } - - struct sockaddr_in client; - int client_socket; - unsigned int len=sizeof(sockaddr_in); - - cout< guard(clients_mtx); - clients.push_back({seed, string("Anonymous"),client_socket,(move(t))}); - } - - for(int i=0; i guard(clients_mtx); + clients.push_back({ seed, + "Anonymous", + client_sock, + move(t), + (seed-1) % NUM_COLORS }); + } + + close(server_sock); + return 0; } -// Set name of client -void set_name(int id, char name[]) -{ - for(int i=0; i guard(clients_mtx); + for (int i = 0; i < (int)clients.size(); i++) + if (clients[i].id == id) return i; + return -1; } -// For synchronisation of cout statements -void shared_print(string str, bool endLine=true) -{ - lock_guard guard(cout_mtx); - cout< guard(cout_mtx); + cout << str; + if (endLine) cout << endl; } -// Broadcast message to all clients except the sender -int broadcast_message(string message, int sender_id) -{ - char temp[MAX_LEN]; - strcpy(temp,message.c_str()); - for(int i=0; i guard(clients_mtx); - clients[i].th.detach(); - clients.erase(clients.begin()+i); - close(clients[i].socket); - break; - } - } +void end_connection(int id) { + lock_guard guard(clients_mtx); + for (auto it = clients.begin(); it != clients.end(); ++it) { + if (it->id == id) { + it->th.detach(); + close(it->socket); + clients.erase(it); + break; + } + } } -void handle_client(int client_socket, int id) -{ - char name[MAX_LEN],str[MAX_LEN]; - recv(client_socket,name,sizeof(name),0); - set_name(id,name); - - // Display welcome message - string welcome_message=string(name)+string(" has joined"); - broadcast_message("#NULL",id); - broadcast_message(id,id); - broadcast_message(welcome_message,id); - shared_print(color(id)+welcome_message+def_col); - - while(1) - { - int bytes_received=recv(client_socket,str,sizeof(str),0); - if(bytes_received<=0) - return; - if(strcmp(str,"#exit")==0) - { - // Display leaving message - string message=string(name)+string(" has left"); - broadcast_message("#NULL",id); - broadcast_message(id,id); - broadcast_message(message,id); - shared_print(color(id)+message+def_col); - end_connection(id); - return; - } - broadcast_message(string(name),id); - broadcast_message(id,id); - broadcast_message(string(str),id); - shared_print(color(id)+name+" : "+def_col+str); - } +void handle_client(int client_socket, int id) { + char name_buf[MAX_LEN], str_buf[MAX_LEN]; + // 1) initial name + recv(client_socket, name_buf, MAX_LEN, 0); + { + lock_guard guard(clients_mtx); + int idx = get_client_index(id); + if (idx >= 0) clients[idx].name = name_buf; + } + int idx = get_client_index(id); + int my_col = clients[idx].color_code; + string join_msg = clients[idx].name + " has joined"; + + broadcast_message("#NULL", id); + broadcast_message(my_col, id); + broadcast_message(join_msg, id); + shared_print(colors[my_col] + join_msg + def_col); + + // 2) message loop + while (true) { + int bytes = recv(client_socket, str_buf, MAX_LEN, 0); + if (bytes <= 0) { + end_connection(id); + return; + } + string msg(str_buf); + + if (msg == "#exit") { + string leave = clients[idx].name + " has left"; + broadcast_message("#NULL", id); + broadcast_message(my_col, id); + broadcast_message(leave, id); + shared_print(colors[my_col] + leave + def_col); + end_connection(id); + return; + } + + // Slash‐commands + if (!msg.empty() && msg[0] == '/') { + if (Commands_Handle(msg, id)) + continue; + } + + // Normal broadcast + idx = get_client_index(id); + my_col = clients[idx].color_code; + broadcast_message(clients[idx].name, id); + broadcast_message(my_col, id); + broadcast_message(msg, id); + shared_print(colors[my_col] + clients[idx].name + + " : " + def_col + msg); + } } diff --git a/server_types.h b/server_types.h new file mode 100644 index 0000000..e043270 --- /dev/null +++ b/server_types.h @@ -0,0 +1,35 @@ +// server_types.h +#ifndef SERVER_TYPES_H +#define SERVER_TYPES_H + +#include +#include +#include +#include + +// One entry per connected client +struct ClientInfo { + int id; + std::string name; + int socket; + std::thread th; + int color_code; +}; + +// Globals (defined in server.cpp): +extern const int MAX_LEN; +extern const int NUM_COLORS; +extern const std::string def_col; +extern const std::string colors[]; + +extern std::vector clients; +extern std::mutex clients_mtx; +extern std::mutex cout_mtx; + +// Helper functions (also defined in server.cpp): +int get_client_index(int id); +int broadcast_message(const std::string& message, int sender_id); +int broadcast_message(int num, int sender_id); +void shared_print(const std::string& str, bool endLine = true); + +#endif // SERVER_TYPES_H From f7e1b605b213dc102a453f902faadf8a10175677 Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 2 Aug 2025 22:27:56 +0530 Subject: [PATCH 2/6] Update README.md --- README.md | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1781436..d531941 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,11 @@ -# Chatroom Application +## About This Project -A chatroom built in C++ using the concepts of socket programming and multi-threading. It supports chatting among multiple clients. +This is a C++ chatroom system built using sockets and multithreading. -![](/screenshot.png) -## How to run +πŸ‘‰ Forked from [cjchirag7/chatroom-cpp](https://github.com/cjchirag7/chatroom-cpp), and extended with the following features: -1. Clone this repository -2. Run the following commands in your terminal : -``` -g++ server.cpp -lpthread -o server -g++ client.cpp -lpthread -o client -``` -3. To run the server application, use this command in the terminal : -``` -./server -``` +- Added XYZ feature (e.g. encryption, command handling, etc.) +- Improved error handling / multithreaded behavior +- Enhanced user experience / documentation -4. Now, open another terminal and use this command to run the client application : -``` -./client -``` - -5. For opening multiple client applications, repeat step 4. \ No newline at end of file +This was done as part of a networking project during [Your College Name]'s placement process. From 2f646b78ab3a3c08e577545a18a57fa470c38a2d Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 2 Aug 2025 22:33:23 +0530 Subject: [PATCH 3/6] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d531941..2bf9704 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ This is a C++ chatroom system built using sockets and multithreading. - Improved error handling / multithreaded behavior - Enhanced user experience / documentation -This was done as part of a networking project during [Your College Name]'s placement process. +This was done as part of a networking project during Thapar's placement process. From 4e61dbfc3971a6300d112b9ba2966cd6a0f45d4a Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 2 Aug 2025 22:35:55 +0530 Subject: [PATCH 4/6] Update README.md --- README.md | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2bf9704..5528111 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,26 @@ -## About This Project +This project is built on top of an existing open-source chatroom system. My contributions include: -This is a C++ chatroom system built using sockets and multithreading. +πŸ’¬ Command System Integration β€” Added support for slash commands: -πŸ‘‰ Forked from [cjchirag7/chatroom-cpp](https://github.com/cjchirag7/chatroom-cpp), and extended with the following features: +/help – Display available commands -- Added XYZ feature (e.g. encryption, command handling, etc.) -- Improved error handling / multithreaded behavior -- Enhanced user experience / documentation +/users – Show a list of online users + +/rename – Change username dynamically + +/msg – Send private messages + +/color <0–5> – Customize your chat text color + +🧡 Modularization β€” Refactored command logic into a separate commands.cpp and commands.h for cleaner architecture + +🌈 Color Customization β€” Implemented client-side support for real-time color changes via command + +πŸ” Thread Safety Enhancements β€” Wrapped access to shared resources (like client list and output) with mutex locks to prevent race conditions + +πŸšͺ Graceful Disconnects β€” Added proper cleanup for user exit (via /quit or Ctrl+C) to avoid dangling threads or broken pipes + +πŸ“ƒ Improved Output Formatting β€” Polished user-facing messages and prompts for better readability and user experience + +πŸ“Έ Added UI Preview β€” Included a screenshot to visualize the interface -This was done as part of a networking project during Thapar's placement process. From 1fb7df21aca0ccb50d569738b705c525e43ffb42 Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 9 Aug 2025 21:04:30 +0530 Subject: [PATCH 5/6] cleaned --- client.cpp | 170 +++++++++++--------------- commands.cpp | 171 ++++++++++---------------- commands.h | 14 +-- server.cpp | 318 ++++++++++++++++++++++++++----------------------- server_types.h | 29 +---- 5 files changed, 312 insertions(+), 390 deletions(-) diff --git a/client.cpp b/client.cpp index 11537ca..7857a9e 100644 --- a/client.cpp +++ b/client.cpp @@ -1,117 +1,89 @@ -// client.cpp -#include -#include -#include -#include #include +#include +#include +#include #include -#include -#include -#define MAX_LEN 200 -#define NUM_COLORS 6 - -using namespace std; - -bool exit_flag = false; -thread t_send, t_recv; -int client_socket; -string def_col = "\033[0m"; -string colors[] = { - "\033[31m", "\033[32m", - "\033[33m", "\033[34m", - "\033[35m", "\033[36m" -}; - -void catch_ctrl_c(int); -void eraseText(int); -void send_message(int); -void recv_message(int); - -int main() { - // 1) socket - if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - perror("socket"); exit(-1); +#include +#include +#include + +#define BUFFER_SIZE 1024 +#define SERVER_PORT 5000 + +void printColor(int colorCode, const std::string& text) { + std::cout << "\033[3" << colorCode << "m" << text << "\033[0m"; +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Usage: ./client \n"; + return 1; } - // 2) connect - sockaddr_in server_addr{}; - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(10000); - server_addr.sin_addr.s_addr = INADDR_ANY; - bzero(&server_addr.sin_zero, 0); - - if (connect(client_socket, - (sockaddr*)&server_addr, - sizeof(server_addr)) == -1) { - perror("connect"); exit(-1); + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror("[ERROR] Socket creation failed"); + return 1; } - signal(SIGINT, catch_ctrl_c); + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(SERVER_PORT); - char name[MAX_LEN]; - cout << "Enter your name : "; - cin.getline(name, MAX_LEN); - send(client_socket, name, sizeof(name), 0); + if (inet_pton(AF_INET, argv[1], &serverAddr.sin_addr) <= 0) { + perror("[ERROR] Invalid address"); + return 1; + } - cout << colors[NUM_COLORS-1] - << "\n\t ====== Welcome to the chat-room ====== " - << def_col << endl; - cout << "Type /help for a list of commands." << endl; + if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { + perror("[ERROR] Connection failed"); + return 1; + } - t_send = thread(send_message, client_socket); - t_recv = thread(recv_message, client_socket); + std::cout << "[INFO] Connected to server at " << argv[1] << ":" << SERVER_PORT << "\n"; - t_send.join(); - t_recv.join(); - return 0; -} + struct pollfd fds[2]; + fds[0].fd = sock; + fds[0].events = POLLIN; + fds[1].fd = STDIN_FILENO; + fds[1].events = POLLIN; -void catch_ctrl_c(int) { - char exit_cmd[] = "#exit"; - send(client_socket, exit_cmd, sizeof(exit_cmd), 0); - exit_flag = true; - t_send.detach(); - t_recv.detach(); - close(client_socket); - exit(0); -} + char buffer[BUFFER_SIZE]; -void eraseText(int cnt) { - while (cnt--) cout << '\b'; -} + while (true) { + int activity = poll(fds, 2, -1); + if (activity < 0) { + perror("[ERROR] poll() failed"); + break; + } -void send_message(int sock) { - while (!exit_flag) { - cout << colors[1] << "You : " << def_col; - char msg[MAX_LEN]; - cin.getline(msg, MAX_LEN); - send(sock, msg, sizeof(msg), 0); - if (strcmp(msg, "#exit") == 0) { - exit_flag = true; - t_recv.detach(); - close(sock); - return; + // Incoming message from server + if (fds[0].revents & POLLIN) { + memset(buffer, 0, BUFFER_SIZE); + int bytesRead = recv(sock, buffer, BUFFER_SIZE, 0); + if (bytesRead <= 0) { + std::cout << "[INFO] Disconnected from server.\n"; + break; + } + std::cout << buffer << std::flush; } - } -} -void recv_message(int sock) { - while (!exit_flag) { - char name[MAX_LEN], msg[MAX_LEN]; - int color_code; - int bytes = recv(sock, name, sizeof(name), 0); - if (bytes <= 0) continue; - recv(sock, &color_code, sizeof(color_code), 0); - recv(sock, msg, sizeof(msg), 0); - - eraseText(6); - if (strcmp(name, "#NULL") != 0) { - cout << colors[color_code] - << name << " : " << def_col << msg << endl; - } else { - cout << colors[color_code] << msg << endl; + // User input + if (fds[1].revents & POLLIN) { + std::string input; + std::getline(std::cin, input); + + if (input.empty()) continue; + + send(sock, input.c_str(), input.size(), 0); + + if (input == "/quit") { + std::cout << "[INFO] Exiting client.\n"; + break; + } } - cout << colors[1] << "You : " << def_col; - fflush(stdout); } + + close(sock); + return 0; } diff --git a/commands.cpp b/commands.cpp index c9def96..7cc53dc 100644 --- a/commands.cpp +++ b/commands.cpp @@ -1,131 +1,80 @@ -// commands.cpp #include "commands.h" -#include "server_types.h" - +#include +#include #include -#include -#include -#include -#include // for send() -#include - -static const int SYS_COLOR = NUM_COLORS - 1; - -void Commands_Init() { - // no-op -} +#include -static void reply_sys(int sock, const std::string& text) { - char buf[MAX_LEN] = {0}; - std::strncpy(buf, text.c_str(), MAX_LEN - 1); - send(sock, "#NULL", MAX_LEN, 0); - send(sock, &SYS_COLOR, sizeof(SYS_COLOR), 0); - send(sock, buf, MAX_LEN, 0); +static void sendToClient(int fd, const std::string& msg) { + send(fd, msg.c_str(), msg.size(), 0); } -bool Commands_Handle(const std::string& input, int sender_id) { - int idx = get_client_index(sender_id); - if (idx < 0) return false; - int sock = clients[idx].socket; +void processCommand(const std::string& cmd, int senderFd, + std::vector& clients, std::mutex& clientsMutex) { + std::istringstream iss(cmd); + std::string command; + iss >> command; - if (input == "/help") { - std::string h = - "Available commands:\n" - " /help - Show this list\n" - " /users - List online users\n" - " /rename - Change your name\n" - " /msg - Private message\n" - " /color <0-5> - Change your color\n"; - reply_sys(sock, h); - return true; + if (command == "/help") { + sendToClient(senderFd, "Commands: /help /users /rename /msg /color <0-5> /quit\n"); } - - if (input == "/users") { - std::ostringstream oss; - oss << "Online users:\n"; - { - std::lock_guard guard(clients_mtx); - for (auto& c : clients) - oss << " - " << c.name << "\n"; + else if (command == "/users") { + std::lock_guard lock(clientsMutex); + std::string list = "Online users:\n"; + for (auto& c : clients) { + list += "- " + c.name + "\n"; } - reply_sys(sock, oss.str()); - return true; + sendToClient(senderFd, list); } - - if (input.rfind("/rename ", 0) == 0) { - std::string new_name = input.substr(8); - if (new_name.empty()) { - reply_sys(sock, "Usage: /rename "); - return true; - } - bool taken = false; - { - std::lock_guard guard(clients_mtx); - for (auto& c : clients) - if (c.name == new_name) { taken = true; break; } - if (!taken) { - std::string old = clients[idx].name; - clients[idx].name = new_name; - std::string note = old + " is now " + new_name; - broadcast_message("#NULL", sender_id); - broadcast_message(SYS_COLOR, sender_id); - broadcast_message(note, sender_id); - shared_print(colors[SYS_COLOR] + note + def_col, true); - reply_sys(sock, "You are now \"" + new_name + "\""); + else if (command == "/rename") { + std::string newName; + iss >> newName; + if (!newName.empty()) { + std::lock_guard lock(clientsMutex); + for (auto& c : clients) { + if (c.fd == senderFd) { + c.name = newName; + sendToClient(senderFd, "Name changed successfully.\n"); + return; + } } } - if (taken) - reply_sys(sock, "Name \"" + new_name + "\" is already taken."); - return true; } + else if (command == "/msg") { + std::string targetUser; + iss >> targetUser; + std::string message; + std::getline(iss, message); + message.erase(0, message.find_first_not_of(" ")); - if (input.rfind("/msg ", 0) == 0) { - std::istringstream iss(input.substr(5)); - std::string target, word, body; - iss >> target; - while (iss >> word) body += word + " "; - if (target.empty() || body.empty()) { - reply_sys(sock, "Usage: /msg "); - return true; - } - int tgt_idx = -1; - { - std::lock_guard guard(clients_mtx); - for (int i = 0; i < (int)clients.size(); i++) - if (clients[i].name == target) { tgt_idx = i; break; } - } - if (tgt_idx < 0) { - reply_sys(sock, "User \"" + target + "\" not found."); + std::lock_guard lock(clientsMutex); + auto it = std::find_if(clients.begin(), clients.end(), + [&](const ClientInfo& c) { return c.name == targetUser; }); + if (it != clients.end()) { + sendToClient(it->fd, "[PM from " + std::to_string(senderFd) + "]: " + message + "\n"); } else { - char nb[MAX_LEN] = {0}, mb[MAX_LEN] = {0}; - std::strncpy(nb, clients[idx].name.c_str(), MAX_LEN - 1); - std::string pref = "(Private) " + body; - std::strncpy(mb, pref.c_str(), MAX_LEN - 1); - int col = clients[idx].color_code; - - send(clients[tgt_idx].socket, nb, MAX_LEN, 0); - send(clients[tgt_idx].socket, &col, sizeof(col), 0); - send(clients[tgt_idx].socket, mb, MAX_LEN, 0); - - reply_sys(sock, "Sent to " + target + ": " + body); + sendToClient(senderFd, "User not found.\n"); } - return true; } - - if (input.rfind("/color ", 0) == 0) { - try { - int c = std::stoi(input.substr(7)); - if (c < 0 || c >= NUM_COLORS) throw std::out_of_range(""); - { - std::lock_guard guard(clients_mtx); - clients[idx].color_code = c; + else if (command == "/color") { + int color; + iss >> color; + if (color >= 0 && color <= 5) { + std::lock_guard lock(clientsMutex); + for (auto& c : clients) { + if (c.fd == senderFd) { + c.colorCode = color; + sendToClient(senderFd, "Color changed.\n"); + return; + } } - reply_sys(sock, "Color set to code " + std::to_string(c)); - } catch (...) { - reply_sys(sock, "Invalid code; use 0–" + std::to_string(NUM_COLORS - 1)); } - return true; } - - return false; + else if (command == "/quit") { + sendToClient(senderFd, "Goodbye!\n"); + shutdown(senderFd, SHUT_RDWR); + close(senderFd); + } + else { + sendToClient(senderFd, "Unknown command. Type /help for help.\n"); + } } diff --git a/commands.h b/commands.h index 9962f2d..713c2a7 100644 --- a/commands.h +++ b/commands.h @@ -1,14 +1,12 @@ -// commands.h #ifndef COMMANDS_H #define COMMANDS_H +#include "server_types.h" #include +#include +#include -// Call once at server startup -void Commands_Init(); +void processCommand(const std::string& cmd, int senderFd, + std::vector& clients, std::mutex& clientsMutex); -// Try to handle a leading-β€˜/’ command from client `sender_id`. -// Returns true if handled (and no further broadcast is needed). -bool Commands_Handle(const std::string& input, int sender_id); - -#endif // COMMANDS_H +#endif diff --git a/server.cpp b/server.cpp index d231a85..3a0c9c4 100644 --- a/server.cpp +++ b/server.cpp @@ -1,181 +1,205 @@ -// server.cpp -#include -#include -#include -#include +#include "server_types.h" +#include "commands.h" #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include -#include "commands.h" -#include "server_types.h" - -using namespace std; - -// globals (also declared extern in server_types.h) -const int MAX_LEN = 200; -const int NUM_COLORS = 6; -const string def_col = "\033[0m"; -const string colors[NUM_COLORS] = { - "\033[31m", "\033[32m", "\033[33m", - "\033[34m", "\033[35m", "\033[36m" -}; - -vector clients; -mutex clients_mtx, cout_mtx; -int seed = 0; - -// forward declarations -int get_client_index(int id); -void shared_print(const string& str, bool endLine=true); -int broadcast_message(const string& message, int sender_id); -int broadcast_message(int num, int sender_id); -void end_connection(int id); -void handle_client(int client_socket, int id); +#define MAX_CLIENTS 50 +#define BUFFER_SIZE 1024 +#define SERVER_PORT 5000 -int main() { - // Prepare command subsystem - Commands_Init(); - - // 1) set up listening socket - int server_sock = socket(AF_INET, SOCK_STREAM, 0); - if (server_sock == -1) { perror("socket"); exit(-1); } +std::vector clients; +std::mutex clientsMutex; - sockaddr_in srv_addr{}; - srv_addr.sin_family = AF_INET; - srv_addr.sin_port = htons(10000); - srv_addr.sin_addr.s_addr = INADDR_ANY; - bzero(&srv_addr.sin_zero, 0); +// Logging queue + mutex +std::queue logQueue; +std::mutex logMutex; +bool running = true; - if (bind(server_sock, (sockaddr*)&srv_addr, sizeof(srv_addr)) == -1) { - perror("bind"); exit(-1); - } - if (listen(server_sock, 8) == -1) { - perror("listen"); exit(-1); +void* loggingThread(void* arg) { + std::ofstream logFile("chatroom.log", std::ios::app); + if (!logFile.is_open()) { + std::cerr << "[ERROR] Could not open log file.\n"; + return nullptr; } - cout << colors[NUM_COLORS-1] - << "\n\t ====== Welcome to the chat-room ====== " - << def_col << endl; - - // 2) accept loop - while (true) { - sockaddr_in cli_addr{}; - socklen_t len = sizeof(cli_addr); - int client_sock = accept(server_sock, - (sockaddr*)&cli_addr, &len); - if (client_sock == -1) { - perror("accept"); continue; + while (running) { + std::string entry; + { + std::lock_guard lock(logMutex); + if (!logQueue.empty()) { + entry = logQueue.front(); + logQueue.pop(); + } } - seed++; - thread t(handle_client, client_sock, seed); - - lock_guard guard(clients_mtx); - clients.push_back({ seed, - "Anonymous", - client_sock, - move(t), - (seed-1) % NUM_COLORS }); - } - - close(server_sock); - return 0; -} - -int get_client_index(int id) { - lock_guard guard(clients_mtx); - for (int i = 0; i < (int)clients.size(); i++) - if (clients[i].id == id) return i; - return -1; -} - -void shared_print(const string& str, bool endLine) { - lock_guard guard(cout_mtx); - cout << str; - if (endLine) cout << endl; -} - -int broadcast_message(const string& message, int sender_id) { - char buf[MAX_LEN] = {0}; - strncpy(buf, message.c_str(), MAX_LEN - 1); - for (auto& c : clients) { - if (c.id != sender_id) - send(c.socket, buf, MAX_LEN, 0); + if (!entry.empty()) { + logFile << entry << std::endl; + logFile.flush(); + } + usleep(10000); // 10ms } - return 0; + logFile.close(); + return nullptr; } -int broadcast_message(int num, int sender_id) { - for (auto& c : clients) { - if (c.id != sender_id) - send(c.socket, &num, sizeof(num), 0); +void broadcastMessage(const std::string& msg, int senderFd) { + std::lock_guard lock(clientsMutex); + for (auto& client : clients) { + if (client.fd != senderFd) { + send(client.fd, msg.c_str(), msg.size(), 0); + } } - return 0; } -void end_connection(int id) { - lock_guard guard(clients_mtx); +void removeClient(int fd) { + std::lock_guard lock(clientsMutex); for (auto it = clients.begin(); it != clients.end(); ++it) { - if (it->id == id) { - it->th.detach(); - close(it->socket); + if (it->fd == fd) { + close(it->fd); clients.erase(it); break; } } } -void handle_client(int client_socket, int id) { - char name_buf[MAX_LEN], str_buf[MAX_LEN]; - // 1) initial name - recv(client_socket, name_buf, MAX_LEN, 0); - { - lock_guard guard(clients_mtx); - int idx = get_client_index(id); - if (idx >= 0) clients[idx].name = name_buf; +int main() { + int server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("[ERROR] Socket creation failed"); + return 1; + } + + int opt = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = INADDR_ANY; + serverAddr.sin_port = htons(SERVER_PORT); + + if (bind(server_fd, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { + perror("[ERROR] Bind failed"); + return 1; } - int idx = get_client_index(id); - int my_col = clients[idx].color_code; - string join_msg = clients[idx].name + " has joined"; - broadcast_message("#NULL", id); - broadcast_message(my_col, id); - broadcast_message(join_msg, id); - shared_print(colors[my_col] + join_msg + def_col); + if (listen(server_fd, 5) < 0) { + perror("[ERROR] Listen failed"); + return 1; + } + + std::cout << "[INFO] Server started on port " << SERVER_PORT << "\n"; + + // Start logging thread + pthread_t logThread; + pthread_create(&logThread, nullptr, loggingThread, nullptr); + + struct pollfd fds[MAX_CLIENTS + 1]; + fds[0].fd = server_fd; + fds[0].events = POLLIN; + + int nfds = 1; - // 2) message loop while (true) { - int bytes = recv(client_socket, str_buf, MAX_LEN, 0); - if (bytes <= 0) { - end_connection(id); - return; - } - string msg(str_buf); - - if (msg == "#exit") { - string leave = clients[idx].name + " has left"; - broadcast_message("#NULL", id); - broadcast_message(my_col, id); - broadcast_message(leave, id); - shared_print(colors[my_col] + leave + def_col); - end_connection(id); - return; + int activity = poll(fds, nfds, -1); + if (activity < 0) { + perror("[ERROR] poll() failed"); + break; } - // Slash‐commands - if (!msg.empty() && msg[0] == '/') { - if (Commands_Handle(msg, id)) + // New connection + if (fds[0].revents & POLLIN) { + sockaddr_in clientAddr{}; + socklen_t clientLen = sizeof(clientAddr); + int newFd = accept(server_fd, (sockaddr*)&clientAddr, &clientLen); + if (newFd < 0) { + perror("[ERROR] accept() failed"); continue; + } + + std::string welcome = "Welcome to Chatroom!\n"; + send(newFd, welcome.c_str(), welcome.size(), 0); + + { + std::lock_guard lock(clientsMutex); + clients.push_back({newFd, "User" + std::to_string(newFd), 0}); + } + + fds[nfds].fd = newFd; + fds[nfds].events = POLLIN; + nfds++; + + std::string logEntry = "[CONNECT] Client FD " + std::to_string(newFd) + " joined."; + { + std::lock_guard lock(logMutex); + logQueue.push(logEntry); + } } - // Normal broadcast - idx = get_client_index(id); - my_col = clients[idx].color_code; - broadcast_message(clients[idx].name, id); - broadcast_message(my_col, id); - broadcast_message(msg, id); - shared_print(colors[my_col] + clients[idx].name - + " : " + def_col + msg); + // Client messages + for (int i = 1; i < nfds; ++i) { + if (fds[i].revents & POLLIN) { + char buffer[BUFFER_SIZE] = {0}; + int bytesRead = recv(fds[i].fd, buffer, BUFFER_SIZE, 0); + + if (bytesRead <= 0) { + std::string logEntry = "[DISCONNECT] Client FD " + std::to_string(fds[i].fd) + " left."; + { + std::lock_guard lock(logMutex); + logQueue.push(logEntry); + } + removeClient(fds[i].fd); + fds[i] = fds[nfds - 1]; + nfds--; + i--; + } else { + buffer[bytesRead] = '\0'; + std::string msg(buffer); + + if (!msg.empty() && msg.back() == '\n') + msg.pop_back(); + + // Process commands + if (!msg.empty() && msg[0] == '/') { + processCommand(msg, fds[i].fd, clients, clientsMutex); + } else { + std::string senderName; + int colorCode = 0; + { + std::lock_guard lock(clientsMutex); + for (auto& c : clients) { + if (c.fd == fds[i].fd) { + senderName = c.name; + colorCode = c.colorCode; + break; + } + } + } + std::string coloredMsg = "\033[3" + std::to_string(colorCode) + "m" + senderName + ": " + msg + "\033[0m\n"; + + std::string logEntry = "[MSG] " + senderName + ": " + msg; + { + std::lock_guard lock(logMutex); + logQueue.push(logEntry); + } + broadcastMessage(coloredMsg, fds[i].fd); + } + } + } + } } + + running = false; + pthread_join(logThread, nullptr); + close(server_fd); + return 0; } diff --git a/server_types.h b/server_types.h index e043270..1cb3873 100644 --- a/server_types.h +++ b/server_types.h @@ -1,35 +1,14 @@ -// server_types.h #ifndef SERVER_TYPES_H #define SERVER_TYPES_H -#include #include #include #include -// One entry per connected client struct ClientInfo { - int id; - std::string name; - int socket; - std::thread th; - int color_code; + int fd; + std::string name; + int colorCode; // 0-5 }; -// Globals (defined in server.cpp): -extern const int MAX_LEN; -extern const int NUM_COLORS; -extern const std::string def_col; -extern const std::string colors[]; - -extern std::vector clients; -extern std::mutex clients_mtx; -extern std::mutex cout_mtx; - -// Helper functions (also defined in server.cpp): -int get_client_index(int id); -int broadcast_message(const std::string& message, int sender_id); -int broadcast_message(int num, int sender_id); -void shared_print(const std::string& str, bool endLine = true); - -#endif // SERVER_TYPES_H +#endif From e3995589a87e2c2a6acdc0f6689c0dfe79f0b17f Mon Sep 17 00:00:00 2001 From: Aditya Jain Date: Sat, 9 Aug 2025 22:38:45 +0530 Subject: [PATCH 6/6] cleaner --- client.cpp | 89 ++++++---------------- commands.cpp | 81 ++------------------ commands.h | 5 +- server.cpp | 203 +++++++++++++++++-------------------------------- server_types.h | 5 +- 5 files changed, 103 insertions(+), 280 deletions(-) diff --git a/client.cpp b/client.cpp index 7857a9e..469b9f1 100644 --- a/client.cpp +++ b/client.cpp @@ -1,89 +1,48 @@ #include #include -#include #include #include -#include +#include #include #include +#include #define BUFFER_SIZE 1024 #define SERVER_PORT 5000 -void printColor(int colorCode, const std::string& text) { - std::cout << "\033[3" << colorCode << "m" << text << "\033[0m"; -} - -int main(int argc, char* argv[]) { - if (argc != 2) { - std::cerr << "Usage: ./client \n"; - return 1; - } +int sockfd; - int sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock < 0) { - perror("[ERROR] Socket creation failed"); - return 1; +void* receiveMessages(void* arg) { + char buffer[BUFFER_SIZE]; + while (true) { + memset(buffer, 0, BUFFER_SIZE); + int bytesRead = recv(sockfd, buffer, BUFFER_SIZE - 1, 0); + if (bytesRead <= 0) break; + std::cout << buffer; } + return nullptr; +} +int main() { + sockfd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in serverAddr{}; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); - if (inet_pton(AF_INET, argv[1], &serverAddr.sin_addr) <= 0) { - perror("[ERROR] Invalid address"); - return 1; - } + connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); - if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { - perror("[ERROR] Connection failed"); - return 1; - } - - std::cout << "[INFO] Connected to server at " << argv[1] << ":" << SERVER_PORT << "\n"; - - struct pollfd fds[2]; - fds[0].fd = sock; - fds[0].events = POLLIN; - fds[1].fd = STDIN_FILENO; - fds[1].events = POLLIN; - - char buffer[BUFFER_SIZE]; + pthread_t recvThread; + pthread_create(&recvThread, nullptr, receiveMessages, nullptr); + std::string msg; while (true) { - int activity = poll(fds, 2, -1); - if (activity < 0) { - perror("[ERROR] poll() failed"); - break; - } - - // Incoming message from server - if (fds[0].revents & POLLIN) { - memset(buffer, 0, BUFFER_SIZE); - int bytesRead = recv(sock, buffer, BUFFER_SIZE, 0); - if (bytesRead <= 0) { - std::cout << "[INFO] Disconnected from server.\n"; - break; - } - std::cout << buffer << std::flush; - } - - // User input - if (fds[1].revents & POLLIN) { - std::string input; - std::getline(std::cin, input); - - if (input.empty()) continue; - - send(sock, input.c_str(), input.size(), 0); - - if (input == "/quit") { - std::cout << "[INFO] Exiting client.\n"; - break; - } - } + std::getline(std::cin, msg); + send(sockfd, msg.c_str(), msg.size(), 0); + if (msg == "/quit") break; } - close(sock); + pthread_cancel(recvThread); + close(sockfd); return 0; } diff --git a/commands.cpp b/commands.cpp index 7cc53dc..52d3809 100644 --- a/commands.cpp +++ b/commands.cpp @@ -1,80 +1,13 @@ #include "commands.h" -#include -#include #include -#include -static void sendToClient(int fd, const std::string& msg) { - send(fd, msg.c_str(), msg.size(), 0); -} - -void processCommand(const std::string& cmd, int senderFd, - std::vector& clients, std::mutex& clientsMutex) { - std::istringstream iss(cmd); - std::string command; - iss >> command; - - if (command == "/help") { - sendToClient(senderFd, "Commands: /help /users /rename /msg /color <0-5> /quit\n"); - } - else if (command == "/users") { - std::lock_guard lock(clientsMutex); - std::string list = "Online users:\n"; - for (auto& c : clients) { - list += "- " + c.name + "\n"; +bool handleCommand(const std::string& msg, int clientSock, std::vector& usersList, std::string& response) { + if (msg == "/users") { + response = "Online users:\n"; + for (auto& name : usersList) { + response += name + "\n"; } - sendToClient(senderFd, list); - } - else if (command == "/rename") { - std::string newName; - iss >> newName; - if (!newName.empty()) { - std::lock_guard lock(clientsMutex); - for (auto& c : clients) { - if (c.fd == senderFd) { - c.name = newName; - sendToClient(senderFd, "Name changed successfully.\n"); - return; - } - } - } - } - else if (command == "/msg") { - std::string targetUser; - iss >> targetUser; - std::string message; - std::getline(iss, message); - message.erase(0, message.find_first_not_of(" ")); - - std::lock_guard lock(clientsMutex); - auto it = std::find_if(clients.begin(), clients.end(), - [&](const ClientInfo& c) { return c.name == targetUser; }); - if (it != clients.end()) { - sendToClient(it->fd, "[PM from " + std::to_string(senderFd) + "]: " + message + "\n"); - } else { - sendToClient(senderFd, "User not found.\n"); - } - } - else if (command == "/color") { - int color; - iss >> color; - if (color >= 0 && color <= 5) { - std::lock_guard lock(clientsMutex); - for (auto& c : clients) { - if (c.fd == senderFd) { - c.colorCode = color; - sendToClient(senderFd, "Color changed.\n"); - return; - } - } - } - } - else if (command == "/quit") { - sendToClient(senderFd, "Goodbye!\n"); - shutdown(senderFd, SHUT_RDWR); - close(senderFd); - } - else { - sendToClient(senderFd, "Unknown command. Type /help for help.\n"); + return true; } + return false; // Not a command we handle here } diff --git a/commands.h b/commands.h index 713c2a7..3f8e07f 100644 --- a/commands.h +++ b/commands.h @@ -1,12 +1,9 @@ #ifndef COMMANDS_H #define COMMANDS_H -#include "server_types.h" #include #include -#include -void processCommand(const std::string& cmd, int senderFd, - std::vector& clients, std::mutex& clientsMutex); +bool handleCommand(const std::string& msg, int clientSock, std::vector& usersList, std::string& response); #endif diff --git a/server.cpp b/server.cpp index 3a0c9c4..9b0b637 100644 --- a/server.cpp +++ b/server.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #define MAX_CLIENTS 50 #define BUFFER_SIZE 1024 @@ -21,185 +22,121 @@ std::vector clients; std::mutex clientsMutex; -// Logging queue + mutex std::queue logQueue; std::mutex logMutex; bool running = true; void* loggingThread(void* arg) { - std::ofstream logFile("chatroom.log", std::ios::app); - if (!logFile.is_open()) { - std::cerr << "[ERROR] Could not open log file.\n"; - return nullptr; - } - + std::ofstream logFile("chat_log.txt", std::ios::app); while (running) { - std::string entry; + std::string msg; { std::lock_guard lock(logMutex); if (!logQueue.empty()) { - entry = logQueue.front(); + msg = logQueue.front(); logQueue.pop(); } } - if (!entry.empty()) { - logFile << entry << std::endl; + if (!msg.empty()) { + logFile << msg << "\n"; logFile.flush(); } - usleep(10000); // 10ms + usleep(10000); } - logFile.close(); return nullptr; } -void broadcastMessage(const std::string& msg, int senderFd) { +void broadcastMessage(const std::string& msg, int excludeFd = -1) { std::lock_guard lock(clientsMutex); for (auto& client : clients) { - if (client.fd != senderFd) { - send(client.fd, msg.c_str(), msg.size(), 0); + if (client.sockfd != excludeFd) { + send(client.sockfd, msg.c_str(), msg.size(), 0); } } } -void removeClient(int fd) { - std::lock_guard lock(clientsMutex); - for (auto it = clients.begin(); it != clients.end(); ++it) { - if (it->fd == fd) { - close(it->fd); - clients.erase(it); - break; - } - } -} +void* clientHandler(void* arg) { + int clientSock = *(int*)arg; + char buffer[BUFFER_SIZE]; + std::string name = "User" + std::to_string(clientSock); -int main() { - int server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) { - perror("[ERROR] Socket creation failed"); - return 1; + { + std::lock_guard lock(clientsMutex); + clients.push_back({clientSock, name}); } - int opt = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + broadcastMessage(name + " has joined\n", clientSock); - sockaddr_in serverAddr{}; - serverAddr.sin_family = AF_INET; - serverAddr.sin_addr.s_addr = INADDR_ANY; - serverAddr.sin_port = htons(SERVER_PORT); + while (true) { + memset(buffer, 0, BUFFER_SIZE); + int bytesRead = recv(clientSock, buffer, BUFFER_SIZE - 1, 0); + if (bytesRead <= 0) break; - if (bind(server_fd, (sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { - perror("[ERROR] Bind failed"); - return 1; + std::string msg(buffer); + std::vector userNames; + { + std::lock_guard lock(clientsMutex); + for (auto& c : clients) userNames.push_back(c.name); + } + + std::string cmdResponse; + if (handleCommand(msg, clientSock, userNames, cmdResponse)) { + send(clientSock, cmdResponse.c_str(), cmdResponse.size(), 0); + } else { + std::string fullMsg = name + ": " + msg; + { + std::lock_guard lock(logMutex); + logQueue.push(fullMsg); + } + broadcastMessage(fullMsg, clientSock); + } + + if (msg == "/quit") break; } - if (listen(server_fd, 5) < 0) { - perror("[ERROR] Listen failed"); - return 1; + close(clientSock); + { + std::lock_guard lock(clientsMutex); + clients.erase(std::remove_if(clients.begin(), clients.end(), + [clientSock](const ClientInfo& c) { return c.sockfd == clientSock; }), + clients.end()); } + broadcastMessage(name + " has left\n"); + return nullptr; +} - std::cout << "[INFO] Server started on port " << SERVER_PORT << "\n"; +int main() { + int serverSock = socket(AF_INET, SOCK_STREAM, 0); + sockaddr_in serverAddr{}; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(SERVER_PORT); + serverAddr.sin_addr.s_addr = INADDR_ANY; + + bind(serverSock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); + listen(serverSock, MAX_CLIENTS); - // Start logging thread pthread_t logThread; pthread_create(&logThread, nullptr, loggingThread, nullptr); - struct pollfd fds[MAX_CLIENTS + 1]; - fds[0].fd = server_fd; - fds[0].events = POLLIN; + std::cout << "Server started on port " << SERVER_PORT << "\n"; + struct pollfd fds[MAX_CLIENTS]; + fds[0].fd = serverSock; + fds[0].events = POLLIN; int nfds = 1; while (true) { - int activity = poll(fds, nfds, -1); - if (activity < 0) { - perror("[ERROR] poll() failed"); - break; - } - - // New connection + poll(fds, nfds, -1); if (fds[0].revents & POLLIN) { - sockaddr_in clientAddr{}; - socklen_t clientLen = sizeof(clientAddr); - int newFd = accept(server_fd, (sockaddr*)&clientAddr, &clientLen); - if (newFd < 0) { - perror("[ERROR] accept() failed"); - continue; - } - - std::string welcome = "Welcome to Chatroom!\n"; - send(newFd, welcome.c_str(), welcome.size(), 0); - - { - std::lock_guard lock(clientsMutex); - clients.push_back({newFd, "User" + std::to_string(newFd), 0}); - } - - fds[nfds].fd = newFd; - fds[nfds].events = POLLIN; - nfds++; - - std::string logEntry = "[CONNECT] Client FD " + std::to_string(newFd) + " joined."; - { - std::lock_guard lock(logMutex); - logQueue.push(logEntry); - } - } - - // Client messages - for (int i = 1; i < nfds; ++i) { - if (fds[i].revents & POLLIN) { - char buffer[BUFFER_SIZE] = {0}; - int bytesRead = recv(fds[i].fd, buffer, BUFFER_SIZE, 0); - - if (bytesRead <= 0) { - std::string logEntry = "[DISCONNECT] Client FD " + std::to_string(fds[i].fd) + " left."; - { - std::lock_guard lock(logMutex); - logQueue.push(logEntry); - } - removeClient(fds[i].fd); - fds[i] = fds[nfds - 1]; - nfds--; - i--; - } else { - buffer[bytesRead] = '\0'; - std::string msg(buffer); - - if (!msg.empty() && msg.back() == '\n') - msg.pop_back(); - - // Process commands - if (!msg.empty() && msg[0] == '/') { - processCommand(msg, fds[i].fd, clients, clientsMutex); - } else { - std::string senderName; - int colorCode = 0; - { - std::lock_guard lock(clientsMutex); - for (auto& c : clients) { - if (c.fd == fds[i].fd) { - senderName = c.name; - colorCode = c.colorCode; - break; - } - } - } - std::string coloredMsg = "\033[3" + std::to_string(colorCode) + "m" + senderName + ": " + msg + "\033[0m\n"; - - std::string logEntry = "[MSG] " + senderName + ": " + msg; - { - std::lock_guard lock(logMutex); - logQueue.push(logEntry); - } - broadcastMessage(coloredMsg, fds[i].fd); - } - } - } + int clientSock = accept(serverSock, nullptr, nullptr); + pthread_t t; + pthread_create(&t, nullptr, clientHandler, &clientSock); + pthread_detach(t); } } running = false; pthread_join(logThread, nullptr); - close(server_fd); + close(serverSock); return 0; } diff --git a/server_types.h b/server_types.h index 1cb3873..5cefbf8 100644 --- a/server_types.h +++ b/server_types.h @@ -2,13 +2,10 @@ #define SERVER_TYPES_H #include -#include -#include struct ClientInfo { - int fd; + int sockfd; std::string name; - int colorCode; // 0-5 }; #endif