This project is a multi-user, terminal-based chat application written entirely in C. It allows users on a local network to dynamically discover and join password-protected chat rooms. The entire application is compiled into a single executable (app), which can act as either a Host (server) or a Joiner (client).
This project was built to demonstrate a deep understanding of core computer systems programming concepts, including low-level socket programming, multithreading, and concurrency management.
| Name | Student ID | GitHub ID |
|---|---|---|
| Prince Patel | 202401151 | Prince-Patel84 |
| Purvi Nakum | 202401172 | Purvi917 |
| Sejal Patel | 202401155 | SejalPatel01 |
| Vishwa Prajapati | 202401163 | Vishwa7399 |
- Single Executable: A single
appbinary handles both hosting and joining. - Host/Join Menu: A simple command-line menu for users to select their role.
- Dynamic Room Discovery: "Joiner" clients automatically scan and display a list of available rooms on the local network.
- Password Protection: Hosts are given a unique, 6-digit password for their room, which joiners must provide.
- Multi-User Chat: Supports a host and multiple clients chatting in real-time.
- Host as Participant: The user who hosts a room is also a participant in the chat.
- Robust & Modular Design: The code is professionally structured to separate concerns (networking, chat UI, host logic, join logic).
This project is a practical application of several key computer systems programming (CSP) concepts.
The application's networking layer is built from the ground up using the low-level BSD socket API. We strategically used both of the main internet protocols for different tasks:
-
TCP (Transmission Control Protocol):
- Analogy: A phone call. It's reliable, connection-based, and guarantees messages arrive in the correct order.
- Use Case: We use TCP for the chat itself (Port 8080). It's essential that chat messages are delivered reliably and in the order they were sent.
- System Calls:
socket(AF_INET, SOCK_STREAM, ...),bind(),listen(),accept(),connect().
-
UDP (User Datagram Protocol):
- Analogy: A postcard or a "shout." It's connectionless, fast, and "fire-and-forget." Packets are not guaranteed to arrive.
- Use Case: We use UDP for room discovery (Port 8888). The Host periodically "broadcasts" a small packet with the room name and password. This is highly efficient, and if a "Joiner" misses one packet, they'll catch the next one a few seconds later.
- System Calls:
socket(AF_INET, SOCK_DGRAM, ...),setsockopt(SO_BROADCAST),sendto(),recvfrom().
This project relies heavily on pthreads to handle multiple tasks concurrently without blocking the user.
- The Problem: A single-threaded program can only do one thing at a time. If the program is waiting for keyboard input, it can't simultaneously receive a message from the server.
- Our Solution: We use threads to manage these conflicting tasks.
- Joiner Client: A simple 2-thread design.
- Main Thread: Runs
start_client_sender()to read keyboard input. - Receiver Thread: Runs
start_client_receiver()to wait for and print server messages.
- Main Thread: Runs
- Host Application: A complex, multi-threaded hybrid server/client.
- Main Thread: Becomes the Host's client (runs
start_client_sender). - Server Listener Thread: Runs
server_listener_thread(), which blocks onaccept()to receive new clients. - Broadcast Thread: Runs
broadcast_room_thread(), whichsleep()s and sends UDP packets. - Host Receiver Thread: Runs
start_client_receiver()for the Host's client. - Client Handler Threads: The server spawns a new
handle_client_thread()for every client that connects.
- Main Thread: Becomes the Host's client (runs
- Joiner Client: A simple 2-thread design.
Because our Host has multiple threads, we have a "shared resource" problem: the global client_list array.
- The Problem (Race Condition): What if Client A and Client B connect at the exact same time?
- Thread A (for Client A) checks
client_listand sees the next open spot isclient_list[0]. - Before it can write, the OS switches tasks.
- Thread B (for Client B) checks
client_listand also sees the next open spot isclient_list[0]. - Thread B writes its data.
- The OS switches back. Thread A writes its data, overwriting Client B.
- Result: Our list is corrupted, and Client B is lost.
- Thread A (for Client A) checks
- Our Solution (
pthread_mutex_t): We use a Mutex (client_list_mutex) as a "lock" to protect theclient_list.- Any function that touches the list (like
add_client_to_list,remove_client_from_list,broadcast_message_to_all,is_name_duplicate) must callpthread_mutex_lock()first. - This ensures only one thread can modify the list at a time, making our server thread-safe.
- Any function that touches the list (like
We designed a robust, line-based protocol to solve the "TCP streaming" bug.
- The Problem: TCP is a stream, not a series of messages. If we
write("PASS")and thenwrite("NAME"), the receiver might get"PASSNAME"in a singleread(). This breaks our logic. - Our Solution: We enforce that every message must end with a newline (
\n).- We created a helper function,
read_line_from_socket(), which reads one byte at a time from a socket until it finds a\n, then returns a clean string. - All senders (
write(),snprintf()) are careful to add a\nto the end of every message, including auth, chat, and system messages. - All receivers (
start_client_receiver,handle_client_thread) useread_line_from_socket()to get one complete, clean message at a time.
- We created a helper function,
The project uses a Makefile to automate compilation.
- It tracks dependencies (
.cfiles,.hfiles). - It links all object files (
.o) into the finalappexecutable. - It correctly passes the
-pthreadflag, which is essential for linking thepthreadslibrary.
The code is separated into five logical modules for clarity and reusability.
| File | Module | Responsibility |
|---|---|---|
chatapp.c |
Main | The main entry point. Its only job is to display the "Host/Join" menu. |
host.h / host.c |
Host Module | Contains all logic for Hosting a room. This includes starting the server threads, managing clients, and the Host's personal client logic. |
join.h / join.c |
Join Module | Contains all logic for Joining a room. This includes network scanning, displaying the room list, and the authentication process. |
chat.h / chat.c |
Chat UI Module | Provides the client-side functions (start_client_sender, start_client_receiver). This is the "UI" of the chat, responsible for keyboard input and screen output. |
networking.h / networking.c |
Networking Library | The core engine. Provides all low-level TCP and UDP functions (setup_tcp_server, connect_to_server, send_broadcast, read_line_from_socket, etc.). |
We designed three custom protocols for our application's services.
- Purpose: To advertise a room's existence.
- Sender: Host
- Receiver: Joiner
- Format: A single plain-text packet:
[RoomName] - Example:
My Test Room
- Purpose: To verify a new client and register their name.
- Flow: This is a strict, line-by-line exchange.
- Client: Sends
[Password]\n - Client: Sends
[Name]\n - Server (On Success): Sends
ACCEPT\n - Server (On Failure): Sends
REJECT_NAME\n(if name is a duplicate) or closes the connection (if password is wrong).
- Client: Sends
- Purpose: To send and receive chat messages after authentication.
- Format: All messages are single-line,
\n-terminated strings. - Examples:
[prince]: Hello world!\n(Sent by a client)[System] Host has joined the room.\n(Sent by the server)
A Makefile is provided for easy compilation.
# Clean any old build files
make clean
# Compile the project
make all