Project 12: Multi-Client Chat Server with Rooms and Private Messages
Build a real-time multi-client chat server where users can join rooms, broadcast messages, send private messages, and see who’s online - demonstrating TCP socket programming, message framing, and efficient broadcasting.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced (Level 3) |
| Time Estimate | 1-2 weeks |
| Language | C |
| Prerequisites | TCP sockets, I/O multiplexing (select/poll/epoll), basic networking |
| Key Topics | Socket programming, message framing, broadcasting, protocol design, room management |
1. Learning Objectives
After completing this project, you will:
- Implement a complete TCP server handling multiple simultaneous clients
- Design and implement a text-based protocol over TCP streams
- Solve the message framing problem (TCP is a byte stream, not message stream)
- Efficiently broadcast messages to subsets of connected clients
- Manage client state including usernames, room memberships, and buffers
- Handle the “slow client” problem correctly
- Build a real-time collaborative application from scratch
2. Theoretical Foundation
2.1 Core Concepts
TCP Is a Byte Stream, Not a Message Stream
This is the #1 source of bugs in network programming. TCP guarantees:
- Bytes arrive in order
- Bytes are not duplicated
- Bytes are not corrupted
TCP does NOT guarantee:
- Message boundaries are preserved
- One send() = one recv()
What Beginners Expect:
───────────────────────────────────────────────────────────
Client send("Hello") ────────> Server recv() -> "Hello"
Client send("World") ────────> Server recv() -> "World"
What Actually Happens (TCP Coalescing):
───────────────────────────────────────────────────────────
Client send("Hello") ──┬──────> Server recv() -> "HelloWorld"
Client send("World") ──┘
Or (TCP Fragmentation):
───────────────────────────────────────────────────────────
Client send("Hello") ────────> Server recv() -> "Hel"
Server recv() -> "loWorld"
Client send("World") ────────> (continuation)
Message Framing Solutions
1. Length-Prefix Framing
┌──────────────────────────────────────────────────────────┐
│ 4 bytes Variable length │
│ ┌─────────────┬──────────────────────────────────────┐ │
│ │ Length │ Message Data │ │
│ │ (uint32) │ (length bytes) │ │
│ └─────────────┴──────────────────────────────────────┘ │
│ │
│ Example: [0x00 0x00 0x00 0x05] [H] [e] [l] [l] [o] │
└──────────────────────────────────────────────────────────┘
2. Delimiter-Based Framing
┌──────────────────────────────────────────────────────────┐
│ Variable length 1-2 bytes │
│ ┌──────────────────┬─────────────┐ │
│ │ Message Data │ Delimiter │ │
│ │ │ (\r\n) │ │
│ └──────────────────┴─────────────┘ │
│ │
│ Example: [H] [e] [l] [l] [o] [\r] [\n] │
│ │
│ Pro: Simple, human-readable │
│ Con: Message can't contain delimiter │
└──────────────────────────────────────────────────────────┘
Broadcasting Efficiently
When alice sends “Hello” to #general with 100 users:
Naive Approach (Blocking):
┌────────────────────────────────────────────────────────────────┐
│ for each user in room: │
│ write(user.fd, message) // BLOCKS if user is slow! │
│ │
│ Problem: One slow client blocks ALL other clients │
└────────────────────────────────────────────────────────────────┘
Correct Approach (Non-Blocking + Buffering):
┌────────────────────────────────────────────────────────────────┐
│ for each user in room: │
│ if (user != sender): │
│ append_to_send_buffer(user, message) │
│ if (send_buffer was empty): │
│ add EPOLLOUT to user's events │
│ │
│ On EPOLLOUT: │
│ write from buffer until EAGAIN │
│ if buffer empty: remove EPOLLOUT │
└────────────────────────────────────────────────────────────────┘
Client State Machine
Connection Lifecycle
┌──────────────┐
│ CONNECTED │
│ (no name) │
└──────┬───────┘
│ Send username
v
┌──────────────┐
│ AUTHENTICATED│
│ (has name) │
└──────┬───────┘
│ /join #room
v
┌──────────────┐
│ IN_ROOM │◄───────┐
│ (can chat) │ │
└──────┬───────┘ │
│ /leave │ /join #other
v │
┌──────────────┐ │
│ LOBBY │────────┘
│ (no room) │
└──────┬───────┘
│ /quit or disconnect
v
┌──────────────┐
│ DISCONNECTED │
└──────────────┘
Room Data Structure
Room Management
┌────────────────────────────────────────────────────────────────┐
│ │
│ rooms: HashMap<String, Room> │
│ │
│ Room { │
│ name: String, │
│ members: HashSet<ClientId>, │
│ topic: String, │
│ created_at: Timestamp, │
│ } │
│ │
│ clients: Array<Client>[MAX_CLIENTS] (indexed by fd) │
│ │
│ Client { │
│ fd: int, │
│ username: String, │
│ current_room: String, │
│ recv_buf: Buffer, │
│ send_buf: Buffer, │
│ state: ConnectionState, │
│ } │
│ │
└────────────────────────────────────────────────────────────────┘
2.2 Why This Matters
Chat servers are foundational to understanding:
- Slack, Discord, IRC: All built on these exact principles
- WebSocket servers: Same concepts, different transport
- Multiplayer games: Real-time state synchronization
- Collaborative editing: Google Docs, Figma
- Pub/sub systems: Message brokers like Kafka, RabbitMQ
Building a chat server teaches you:
- Protocol design (you’ll appreciate HTTP/WebSocket more)
- Handling concurrent state (rooms, users, messages)
- The complexity of “simple” real-time applications
2.3 Historical Context
- 1988: IRC (Internet Relay Chat) created by Jarkko Oikarinen
- 1990s: IRC became the dominant real-time chat protocol
- 2003: Skype launched with P2P architecture
- 2009: WhatsApp launched using XMPP
- 2013: Slack launched, eventually moving to custom protocol
- 2015: Discord launched with WebSocket + custom protocol
- Today: All use variations of the patterns you’ll implement
2.4 Common Misconceptions
Misconception 1: “TCP preserves message boundaries” False. TCP is a byte stream. You must implement framing.
Misconception 2: “send() always sends all data” False. send() may send partial data (returns bytes sent). You must loop.
Misconception 3: “recv() returns one complete message” False. recv() returns whatever bytes are available, which may be partial or multiple messages.
Misconception 4: “I can ignore slow clients” Dangerous. A slow client can block your entire server if you use blocking writes, or exhaust memory if you buffer infinitely.
3. Project Specification
3.1 What You Will Build
A multi-client chat server that:
- Accepts TCP connections and assigns usernames
- Supports chat rooms that users can join/leave
- Broadcasts messages to all users in a room
- Supports private messages between users
- Shows who’s online in each room
- Handles disconnections gracefully
3.2 Functional Requirements
| ID | Requirement |
|---|---|
| F1 | Accept client connections on configurable port |
| F2 | Require username on connect |
| F3 | /join #room - join or create a room |
| F4 | /leave - leave current room |
| F5 | /rooms - list all active rooms |
| F6 | /users - list users in current room |
| F7 | /msg username text - send private message |
| F8 | /quit - disconnect cleanly |
| F9 | Broadcast room messages to all room members |
| F10 | Notify room when user joins/leaves |
3.3 Non-Functional Requirements
| ID | Requirement |
|---|---|
| N1 | Support 100+ concurrent clients |
| N2 | Messages delivered in <100ms |
| N3 | No message loss under normal conditions |
| N4 | Graceful handling of misbehaving clients |
| N5 | Clean resource deallocation on disconnect |
3.4 Protocol Specification
Client -> Server Messages:
─────────────────────────────────────────────────────────
USERNAME <name> # Set username (first message)
JOIN <room> # Join a room
LEAVE # Leave current room
MSG <text> # Send message to room
PRIVMSG <user> <text> # Private message
ROOMS # List rooms
USERS # List users in room
QUIT # Disconnect
Server -> Client Messages:
─────────────────────────────────────────────────────────
OK # Command succeeded
ERROR <message> # Command failed
ROOM <room> <user> <text> # Room message
PRIVMSG <from> <text> # Private message received
JOIN <room> <user> # User joined room
LEAVE <room> <user> # User left room
USERLIST <user1> <user2> ... # Response to USERS
ROOMLIST <room1> <room2> ... # Response to ROOMS
All messages terminated by \r\n
3.5 Example Usage / Output
# Terminal 1: Start server
$ ./mychatd -p 9999
Chat server started on port 9999
Waiting for connections...
# Terminal 2: Client 1 (alice)
$ nc localhost 9999
USERNAME alice
OK Welcome, alice!
JOIN #general
OK Joined #general (0 users)
Hello everyone!
ROOM #general alice Hello everyone!
# Terminal 3: Client 2 (bob)
$ nc localhost 9999
USERNAME bob
OK Welcome, bob!
JOIN #general
OK Joined #general (1 users)
JOIN #general alice # (notification that alice is here)
ROOM #general alice Hello everyone!
Hi alice!
ROOM #general bob Hi alice!
# Back on Terminal 2 (alice sees bob's message)
JOIN #general bob
ROOM #general bob Hi alice!
# Alice sends private message
PRIVMSG bob Hey, private message!
OK Sent
# Bob receives:
PRIVMSG alice Hey, private message!
# List users
USERS
USERLIST alice bob
# List rooms
ROOMS
ROOMLIST #general
# Leave and quit
LEAVE
OK Left #general
QUIT
OK Goodbye!
3.6 Real World Outcome
After completing this project, you will have:
- A working chat server you can demo
- Understanding of how Slack/Discord work internally
- Protocol design experience valuable for any networked application
- Foundation for building real-time collaborative features
4. Solution Architecture
4.1 High-Level Design
┌──────────────────────────────────────┐
Clients │ Chat Server │
┌─────────┐ │ │
│ Client1 │◄───┼───►┌──────────────────────────────┐ │
└─────────┘ │ │ Connection Manager │ │
┌─────────┐ │ │ (epoll/select event loop) │ │
│ Client2 │◄───┼───►├──────────────────────────────┤ │
└─────────┘ │ │ Protocol Parser │ │
┌─────────┐ │ │ (message framing) │ │
│ Client3 │◄───┼───►├──────────────────────────────┤ │
└─────────┘ │ │ Command Handler │ │
... │ │ (join/leave/msg/etc) │ │
│ ├──────────────────────────────┤ │
│ │ Room Manager │ │
│ │ (rooms, members, broadcast) │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
4.2 Key Components
Client Structure
#define MAX_USERNAME 32
#define MAX_ROOM 32
#define BUFFER_SIZE 4096
typedef enum {
STATE_CONNECTED, // Just connected, no username
STATE_AUTHENTICATED, // Has username
STATE_IN_ROOM, // In a room
} client_state_t;
typedef struct {
int fd;
client_state_t state;
char username[MAX_USERNAME];
char current_room[MAX_ROOM];
char recv_buf[BUFFER_SIZE];
size_t recv_len;
char send_buf[BUFFER_SIZE];
size_t send_len;
size_t send_offset;
time_t last_active;
} client_t;
Room Structure
#define MAX_ROOM_MEMBERS 100
typedef struct {
char name[MAX_ROOM];
int member_fds[MAX_ROOM_MEMBERS];
int member_count;
char topic[256];
time_t created_at;
} room_t;
typedef struct {
room_t rooms[MAX_ROOMS];
int room_count;
} room_manager_t;
4.3 Data Structures
| Structure | Purpose | Implementation |
|---|---|---|
| clients[] | Client state by fd | Array indexed by fd |
| rooms[] | Active rooms | Dynamic array or hash map |
| username_to_fd | Find client by name | Hash map (for /msg) |
4.4 Algorithm Overview
Message Parsing (Newline Delimited)
1. Append received bytes to recv_buf
2. Search for \n (or \r\n) in buffer
3. If found:
a. Extract message (bytes before delimiter)
b. Process message (call command handler)
c. Shift remaining bytes to buffer start
d. Repeat from step 2 (may have multiple messages)
4. If not found: wait for more data
Broadcast to Room
function broadcast(room, sender, message):
for each member in room.members:
if member != sender:
format_message = "ROOM " + room.name + " " + sender.username + " " + message
queue_send(member, format_message)
function queue_send(client, message):
append message + "\r\n" to client.send_buf
if send_buf was empty:
add EPOLLOUT to client's events
5. Implementation Guide
5.1 Development Environment Setup
# Create project directory
mkdir chat-server && cd chat-server
# Create source files
touch main.c server.c server.h client.c client.h room.c room.h protocol.c protocol.h
# Compile
gcc -Wall -Wextra -O2 -o mychatd main.c server.c client.c room.c protocol.c
# For debugging
gcc -Wall -Wextra -g -fsanitize=address -o mychatd-debug \
main.c server.c client.c room.c protocol.c
5.2 Project Structure
chat-server/
├── Makefile
├── main.c # Entry point, argument parsing
├── server.h # Server API
├── server.c # Event loop, connection management
├── client.h # Client structure and operations
├── client.c # Client state management
├── room.h # Room structure and operations
├── room.c # Room management, broadcasting
├── protocol.h # Protocol constants and parsing
├── protocol.c # Message parsing and formatting
└── tests/
├── test_protocol.c # Protocol parsing tests
└── test_room.c # Room management tests
5.3 The Core Question You’re Answering
“How do you efficiently route messages between multiple clients while maintaining low latency and proper isolation?”
This decomposes into:
- How do you handle multiple connections? (I/O multiplexing)
- How do you parse messages from a byte stream? (framing)
- How do you route messages to the right clients? (rooms, user lookup)
- How do you avoid blocking on slow clients? (non-blocking I/O, buffering)
5.4 Concepts You Must Understand First
| Question | Book Reference |
|---|---|
| How do socket(), bind(), listen(), accept() work? | UNIX Network Programming Ch. 4 |
| What is the difference between blocking and non-blocking sockets? | APUE Ch. 14 |
| How does select() or epoll work? | TLPI Ch. 63 |
| What is Nagle’s algorithm and should you disable it? | TCP/IP Illustrated Vol. 1 |
| What happens when send buffer is full? | UNIX Network Programming Ch. 5 |
5.5 Questions to Guide Your Design
Protocol Design
- Binary or text protocol? Why?
- How to handle messages containing spaces?
- What if username contains special characters?
- How long can a message be?
Room Management
- How to efficiently find all members of a room?
- What happens to a room when the last person leaves?
- Can a user be in multiple rooms? (Simplify: no)
Error Handling
- What if username is already taken?
- What if room doesn’t exist?
- What if target of /msg is offline?
Scalability
- What if one client’s send buffer fills up?
- How to handle a flood of messages?
- Should there be rate limiting?
5.6 Thinking Exercise
Trace this scenario:
alice connects, sets username, joins #general
bob connects, sets username, joins #general
alice sends "Hello"
Step by step:
1. alice connects: fd=5
- epoll_wait returns EPOLLIN on listen_fd
- accept() returns 5
- epoll_ctl(ADD, fd=5, EPOLLIN)
- clients[5] = new client, state=CONNECTED
2. alice sends "USERNAME alice\r\n"
- epoll_wait returns EPOLLIN on fd=5
- read(5) -> "USERNAME alice\r\n"
- parse: command=USERNAME, args="alice"
- clients[5].username = "alice"
- clients[5].state = AUTHENTICATED
- queue_send(5, "OK Welcome, alice!\r\n")
3. alice sends "JOIN #general\r\n"
- parse: command=JOIN, args="#general"
- room = find_or_create_room("#general")
- room.members.add(5)
- clients[5].current_room = "#general"
- clients[5].state = IN_ROOM
- queue_send(5, "OK Joined #general (0 users)\r\n")
4. bob connects, authenticates, joins #general
- Similar to alice
- When joining: room.members = [5, 6]
- broadcast_to_room("#general", NULL, "JOIN #general bob\r\n")
- queue_send(5, "JOIN #general bob\r\n") # alice notified
- queue_send(6, "OK Joined #general (1 users)\r\n")
5. alice sends "MSG Hello\r\n"
- parse: command=MSG, args="Hello"
- sender = clients[5]
- room = find_room(sender.current_room)
- formatted = "ROOM #general alice Hello\r\n"
- broadcast_to_room(room, sender, formatted)
- queue_send(5, formatted) # echo to alice
- queue_send(6, formatted) # send to bob
Questions:
- What if bob's send buffer is full when alice sends "Hello"?
- What if bob disconnects while we're iterating members?
- What if alice sends 1000 messages before bob reads any?
5.7 Hints in Layers
Hint 1: Message Framing (Conceptual) Use newline-delimited messages for simplicity. Search for ‘\n’ in the receive buffer to find message boundaries.
Hint 2: Parsing a Line (More Specific)
// Find complete line in buffer
char *find_line(client_t *c) {
for (size_t i = 0; i < c->recv_len; i++) {
if (c->recv_buf[i] == '\n') {
c->recv_buf[i] = '\0'; // Null-terminate
// Handle \r\n
if (i > 0 && c->recv_buf[i-1] == '\r')
c->recv_buf[i-1] = '\0';
return c->recv_buf;
}
}
return NULL; // No complete line
}
// After processing, shift buffer
void consume_line(client_t *c, size_t len) {
memmove(c->recv_buf, c->recv_buf + len + 1,
c->recv_len - len - 1);
c->recv_len -= (len + 1);
}
Hint 3: Command Parsing (Technical Details)
typedef enum {
CMD_USERNAME, CMD_JOIN, CMD_LEAVE, CMD_MSG,
CMD_PRIVMSG, CMD_ROOMS, CMD_USERS, CMD_QUIT, CMD_UNKNOWN
} command_t;
command_t parse_command(const char *line, char **args) {
char *space = strchr(line, ' ');
char cmd[32];
if (space) {
size_t len = space - line;
strncpy(cmd, line, len);
cmd[len] = '\0';
*args = space + 1;
} else {
strcpy(cmd, line);
*args = NULL;
}
if (strcmp(cmd, "USERNAME") == 0) return CMD_USERNAME;
if (strcmp(cmd, "JOIN") == 0) return CMD_JOIN;
// ... etc
return CMD_UNKNOWN;
}
Hint 4: Broadcasting Without Blocking
void broadcast_to_room(room_t *room, client_t *sender, const char *msg) {
for (int i = 0; i < room->member_count; i++) {
client_t *c = get_client(room->member_fds[i]);
if (c && c != sender) {
queue_message(c, msg);
}
}
}
void queue_message(client_t *c, const char *msg) {
size_t len = strlen(msg);
if (c->send_len + len + 2 > BUFFER_SIZE) {
// Buffer full - drop message or disconnect client
fprintf(stderr, "Send buffer full for %s\n", c->username);
return;
}
memcpy(c->send_buf + c->send_len, msg, len);
c->send_len += len;
c->send_buf[c->send_len++] = '\r';
c->send_buf[c->send_len++] = '\n';
// Make sure we're listening for EPOLLOUT
modify_events(c->fd, EPOLLIN | EPOLLOUT);
}
5.8 The Interview Questions They’ll Ask
- “How do you handle the case where TCP combines multiple messages?”
- Implement message framing (length-prefix or delimiter)
- Buffer incoming data
- Parse and process complete messages only
- Keep partial messages for next read
- “What happens if a client stops reading (slow consumer)?”
- Send buffer fills up, write() returns EAGAIN
- Options: (a) buffer messages (memory limit), (b) drop messages, (c) disconnect slow client
- Production systems usually have buffer limits + timeouts
- “How would you scale this to multiple servers?”
- Use Redis pub/sub for cross-server messages
- Or dedicated message broker (Kafka, RabbitMQ)
- Store room state in shared database
- Implement sticky sessions or message routing
- “What’s the difference between TCP and UDP for chat?”
- TCP: reliable, ordered, connection-oriented
- UDP: no guarantees, but lower latency
- Chat typically uses TCP for reliability
- Games might use UDP with custom reliability layer
- “How would you add message persistence?”
- Store messages in database (PostgreSQL, MongoDB)
- Buffer recent messages in memory
- On join, send last N messages
- Consider message IDs for deduplication
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Socket basics | UNIX Network Programming Vol 1 | Ch. 1-5 |
| I/O multiplexing | UNIX Network Programming Vol 1 | Ch. 6 |
| TCP details | TCP/IP Illustrated Vol 1 | Ch. 12-17 |
| Protocol design | Designing Data-Intensive Applications | Ch. 4 |
6. Testing Strategy
6.1 Unit Tests
// Test message parsing
void test_parse_username(void) {
char *args;
command_t cmd = parse_command("USERNAME alice", &args);
assert(cmd == CMD_USERNAME);
assert(strcmp(args, "alice") == 0);
}
void test_parse_msg(void) {
char *args;
command_t cmd = parse_command("MSG Hello World", &args);
assert(cmd == CMD_MSG);
assert(strcmp(args, "Hello World") == 0);
}
// Test room operations
void test_join_room(void) {
room_t *room = find_or_create_room("#test");
assert(room != NULL);
assert(strcmp(room->name, "#test") == 0);
room_add_member(room, 5);
assert(room->member_count == 1);
assert(room_has_member(room, 5));
}
6.2 Integration Tests
# Test basic flow with netcat
(
echo "USERNAME alice"
sleep 0.1
echo "JOIN #test"
sleep 0.1
echo "MSG Hello"
sleep 0.1
echo "QUIT"
) | nc localhost 9999
# Test multiple clients
for i in {1..10}; do
(
echo "USERNAME user$i"
echo "JOIN #stress"
echo "MSG Message from user$i"
sleep 1
echo "QUIT"
) | nc localhost 9999 &
done
wait
6.3 Edge Cases to Test
| Scenario | Expected Behavior |
|---|---|
| Username already taken | ERROR Username taken |
| Join non-existent room | Create room |
| MSG without joining room | ERROR Not in a room |
| PRIVMSG to offline user | ERROR User not found |
| Very long message | Truncate or reject |
| Binary data in message | Handle safely |
| Rapid connect/disconnect | No resource leak |
7. Common Pitfalls & Debugging
| Problem | Symptom | Root Cause | Fix |
|---|---|---|---|
| Messages merged | “HelloWorld” instead of “Hello” + “World” | No message framing | Implement delimiter parsing |
| Messages split | Partial message received | Didn’t buffer partial reads | Append to buffer until delimiter |
| Server hangs | Stops responding | Blocking write to slow client | Non-blocking I/O + buffering |
| Memory growth | Memory keeps increasing | Not cleaning up disconnected clients | Proper cleanup on disconnect |
| Lost notifications | Join/leave not delivered | Modified member list during iteration | Copy list or use safe iteration |
Debugging Tips
- Log all protocol messages
void debug_log(const char *direction, int fd, const char *msg) { fprintf(stderr, "[%s fd=%d] %s\n", direction, fd, msg); } - Test with telnet/nc for manual interaction
telnet localhost 9999 # or nc localhost 9999 - Check for file descriptor leaks
lsof -p $(pgrep mychatd) | wc -l - Use Wireshark to see actual traffic
sudo tcpdump -i lo port 9999 -A
8. Extensions & Challenges
| Extension | Difficulty | Concepts Learned |
|---|---|---|
| Add SSL/TLS | Medium | OpenSSL integration |
| WebSocket support | Medium | Protocol upgrade, framing |
| Message history | Easy | Ring buffer or database |
| Rate limiting | Easy | Token bucket algorithm |
| Multiple rooms per user | Medium | State machine complexity |
| File sharing | Hard | Large data transfer |
| User authentication | Medium | Password hashing, sessions |
9. Real-World Connections
How Production Systems Solve This
| System | Approach |
|---|---|
| IRC | Plain TCP, text protocol, channels |
| Slack | WebSocket, JSON messages, Redis pub/sub |
| Discord | WebSocket, binary protocol, voice servers |
| XMPP-based, end-to-end encryption |
Industry Relevance
- Every real-time application uses these patterns
- WebSocket is essentially TCP + message framing + HTTP upgrade
- Understanding this helps with any collaborative feature
- Directly applicable to gaming, finance, IoT
10. Resources
Primary References
- Stevens, W.R. “UNIX Network Programming, Vol 1” - Ch. 1-6
- Stevens, W.R. “TCP/IP Illustrated, Vol 1” - Ch. 1-2
- RFC 1459 (IRC Protocol) - For inspiration
Online Resources
11. Self-Assessment Checklist
Before considering this project complete, verify:
- Multiple clients can connect simultaneously
- Usernames are validated and stored
- Rooms can be created and joined
- Messages broadcast to all room members
- Private messages work between users
- /users and /rooms commands work
- Disconnection handled gracefully
- Other users notified of join/leave
- No message loss under normal load
- No file descriptor or memory leaks
- Handles malformed input without crashing
- Can explain message framing strategy
12. Completion Criteria
This project is complete when:
- All functional requirements (F1-F10) are implemented
- Two clients can have a conversation in a room
- Private messages work correctly
- Disconnection notifies other room members
- Server handles 50+ concurrent connections
- No resource leaks after stress testing
- You can explain the message framing solution
Deliverables:
- Server source code with clear comments
- Simple test client (or use netcat)
- Protocol documentation
- README with usage instructions
- Brief writeup on design decisions
This project teaches the fundamentals of real-time networked applications. Every chat app, multiplayer game, and collaborative tool is built on these exact principles. Master this, and you understand how Discord, Slack, and Figma achieve real-time collaboration.