This project implements a multi-client chat application in C++ using TCP sockets. The system supports real-time communication between users and is designed to explore different concurrency models for handling multiple simultaneous client connections.
The system consists of three main components:
- Chat Server – Manages authenticated client connections and message routing.
- Discovery Server – Handles user registration and credential validation.
- Chat Client – Provides a terminal-based interface for users to interact with the chat system.
The assignment also includes performance benchmarking to compare different server concurrency approaches under load and stress conditions.
- Client registers/logs in via the Discovery Server (port 8000).
- Discovery Server validates credentials.
- Client connects to the Chat Server (port 8080).
- Chat Server manages message routing between active users.
- Monitoring module collects performance metrics during benchmarking.
The chat server is implemented using three different concurrency models:
Approach:
- Uses the
fork()system call. - Creates a new child process for each client connection.
- Uses pipes for inter-process communication (IPC) between child processes and the parent process.
- Parent process acts as a message router, reading from child pipes and forwarding to the appropriate recipient pipe.
Key Characteristics:
- Process-level isolation ensures fault containment.
- Higher VmRSS memory overhead (each child is counted separately), but PSS remains low due to copy-on-write page sharing.
- No shared-state complexity within children — each child handles exactly one client.
Expected Behavior Under Load:
- Increased VmRSS with client count, PSS grows more slowly.
- Higher latency variance due to
fork()cost per connection. - Stable isolation between clients.
Approach:
- Uses
pthread_create()to spawn a new thread per client. - All threads share the same address space.
- Shared state (active users, socket map) protected by a single
pthread_mutex_t.
Key Characteristics:
- Shared memory model allows direct in-process message forwarding.
- Lower memory overhead than fork-based approach.
- Faster context switching compared to processes.
- Mutex contention can become a bottleneck at high concurrency.
Expected Behavior Under Load:
- Low and stable latency.
- Flat memory usage as threads share the process heap.
- CPU scales with client count; mutex lock contention may cause spikes.
Approach:
- Uses
select()to monitor multiple socket descriptors for readiness. - Single process and single thread handles all client sockets.
- Event-driven I/O model — only acts when a socket is ready.
Key Characteristics:
- No per-client thread or process overhead.
- Lowest memory consumption of the three implementations.
- Hard limit of 30 simultaneous clients (
MAX_CLIENTS). - O(n) descriptor scanning on every
select()call.
Expected Behavior Under Load:
- Most memory-efficient.
- CPU grows faster than pthread at high client counts due to O(n) fd scanning.
- Hard scalability ceiling at MAX_CLIENTS.
- Uses
pthread_create()to spawn a dedicated receive thread; the main thread handles sending. - Separates sending and receiving logic to allow asynchronous message handling.
- Terminal-based UI with a command interface.
- Connects to the discovery server first for authentication, then to the chat server for messaging.
- User registration and authentication
- Broadcast messaging
- Private messaging
- Active user listing
- Graceful login and logout handling
- Performance monitoring and logging
- Persistent chat history storage
- User-specific chat history retrieval
- User state management (active/busy/away)
- TCP is used for reliable, ordered communication.
- Custom application-layer protocol with a 4-byte big-endian length prefix followed by the message body.
- Wire format:
[uint32 network-byte-order length][body]
- The length prefix uses
htonl/ntohlto handle endianness correctly across platforms.
MessageType|Sender|Receiver|Content
| Type | Description |
|---|---|
| LOGIN | Login request |
| LOGIN_SUCCESSFUL | Successfully logged in |
| LOGIN_UNSUCCESSFUL | Wrong credentials |
| LOGIN_REPEAT | User already logged in |
| BROADCAST | Send message to all users |
| PRIVATE | Send message to one receiver |
| USER_NOT_ACTIVE | Receiver is registered but offline |
| USER_NOT_FOUND | Receiver username does not exist |
| LOGOUT | Signal for logging out |
| ACTIVE_USERS | Request/response for online user list |
| REGISTER | Registration request |
| REGISTERED | Registration successful |
| REGISTRATION_UNSUCCESSFUL | Username already taken |
| INVALID | Invalid instruction |
| HISTORY | Request for a user's chat history |
| CHANGE_STATE | Request to change user state |
| CHANGE_STATE_FAIL | Invalid state provided |
| UNKNOWN | Generic fallback for unexpected errors |
Build all binaries:
make allBuild individual targets:
make pthread # client + discovery + server_pthread
make fork # client + discovery + server_fork
make select # client + discovery + server_selectClean build artifacts:
make cleanMust be started before the chat server and any clients.
./build/discoveryListens on port 8000.
Start one of the three implementations:
./build/server_pthread # thread-per-client
./build/server_fork # process-per-client
./build/server_select # select()-based non-blockingAll servers listen on port 8080.
./build/clientOn startup:
/register <username> <password> -> register a new account
/login <username> <password> -> log in to an existing account
Once logged in:
/bc <message> -> broadcast to all online users
/dm <username> <message> -> private message to a specific user
/here -> list all currently online users
/logout -> disconnect gracefully
/help -> show command list
- Start the discovery server and a chat server.
- Run
./build/clientand register with/register alice pass. - Run a second client and register with
/register bob pass. - Verify both clients reach the chat prompt.
- Stop and restart both clients, use
/login alice passand/login bob passto verify credentials persist.
- Connect at least two clients.
- From one client, run
/bc hello everyone. - Verify the message appears on all other connected clients.
- Verify the sender does not receive their own broadcast.
- Connect two clients (alice and bob).
- From alice, run
/dm bob secret message. - Verify bob receives
[Whisper][alice]: secret message. - Verify no other connected clients see the message.
- Try
/dm nonexistent hi— verifyUser does not exist!. - Log out bob and try
/dm bob hifrom alice — verifyUser is not online right now.
- Connect three clients.
- From any client, run
/here. - Verify all three usernames appear.
- Log one client out and run
/hereagain — verify the list updates.
- Connect a client and log out with
/logout. - Reconnect and log in again — verify no
LOGIN_REPEATerror. - Without logging out, kill the client process (
Ctrl+C). - Verify the server handles the abrupt disconnect and removes the user from the active list.
Server Metrics:
- CPU usage (%) — sampled via
psevery 5 seconds - VmRSS — physical RAM used, shared libraries counted in full (from
/proc/<pid>/status) - PSS — physical RAM used, shared pages split proportionally (from
/proc/<pid>/smaps_rollup); more accurate than VmRSS for fork-based servers since copy-on-write shared pages are divided proportionally across processes
Client Metrics:
- Message delivery latency — time from
send()to arrival in the receiver's buffer, measured usingtime.perf_counter()in Python
- Fixed number of concurrent client pairs (1, 2, 5, 7, 10).
- Each sender sends 200 private messages to its paired receiver.
- Resource metrics sampled throughout.
- Client count progressively increased across runs.
- CPU, memory, and latency observed as load grows.
- Bottlenecks identified from degradation points.
A separate monitoring thread runs alongside each test. Every 5 seconds it:
- Reads CPU% via
ps -p <pid> -o %cpu - Reads VmRSS from
/proc/<pid>/status - Reads PSS from
/proc/<pid>/smaps_rollup - For the fork server, sums all metrics across the parent and all child processes
- Appends one row to
output/resource_log.csv
Latency values are written to output/latency_log.csv immediately after each message round-trip is timed.
Python scripts are used for all plotting. Run the testbench to execute tests and generate plots automatically:
python3 testbench.pyPlots generated in output/:
plot1_latency_distribution.png— box plot of latency distribution for each server at each client countplot2_cpu_memory_vs_clients.png— line plots of CPU%, VmRSS, and PSS vs number of client pairs
See report.pdf.
Covers:
- Methodology
- Test results with graphs and data tables
- Observations and analysis (latency, CPU, VmRSS vs PSS interpretation)
- Bottlenecks identified
- Potential optimizations