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:

  1. Accepts TCP connections and assigns usernames
  2. Supports chat rooms that users can join/leave
  3. Broadcasts messages to all users in a room
  4. Supports private messages between users
  5. Shows who’s online in each room
  6. 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:

  1. How do you handle multiple connections? (I/O multiplexing)
  2. How do you parse messages from a byte stream? (framing)
  3. How do you route messages to the right clients? (rooms, user lookup)
  4. 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

  1. “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
  2. “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
  3. “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
  4. “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
  5. “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

  1. 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);
    }
    
  2. Test with telnet/nc for manual interaction
    telnet localhost 9999
    # or
    nc localhost 9999
    
  3. Check for file descriptor leaks
    lsof -p $(pgrep mychatd) | wc -l
    
  4. 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
WhatsApp 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:

  1. All functional requirements (F1-F10) are implemented
  2. Two clients can have a conversation in a room
  3. Private messages work correctly
  4. Disconnection notifies other room members
  5. Server handles 50+ concurrent connections
  6. No resource leaks after stress testing
  7. 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.