A multithreaded TCP group chat server and client written in C using POSIX sockets and pthreads, with a shared thread-safe message queue and a coordinated, graceful shutdown mechanism.
- Multi-client chat server — every connection is handled on its own thread, so many clients can chat concurrently.
- Producer/consumer message queue — client threads enqueue incoming messages; a single broadcaster thread dequeues and relays them, all protected by mutexes and condition variables.
- Graceful shutdown — a companion
shutdown_serverutility stops the server cleanly: it stops accepting new clients, finishes broadcasting anything still queued, notifies every connected client, and waits for them all to disconnect before exiting. - Simple line-based protocol — a client's first message is treated as its username; everything sent after that is broadcast as chat.
- Bold usernames — messages are wrapped in ANSI escape codes so usernames stand out in the terminal.
.
├── server.c # Chat server
├── client.c # Chat client
├── shutdown-server.c # Utility that triggers a graceful shutdown
├── Makefile
├── LICENSE
└── README.md
- Linux, macOS, or another POSIX-compliant system
gccandmake- IPv4 networking
make # builds server, client, and shutdown_server
make server # or build just one target at a time
make client
make shutdown_server
make clean # remove built binaries./server <port>$ ./server 8080
Server is ready to accept connections on port 8080
The server binds to all network interfaces, so remote clients can connect too — just make sure the port is reachable through any firewall.
./client <server_ip> <server_port>$ ./client 127.0.0.1 8080
What do you like to be known as?
Username: alice
Connection established Server: 127.0.0.1/'8080' as 'alice'
CLIENT:- SERVER:-
hello everyone!
Usernames must be 3–25 characters with no spaces. Open several terminals with ./client 127.0.0.1 8080 to try a group chat. Type exit in a client to disconnect.
Run this on the same machine as the server:
./shutdown_server <port>For safety, the server only honors a shutdown request whose sender's IP is 127.0.0.1.
Message flow — each client is handled by its own receive thread. Chat messages are pushed onto a shared queue, and a single broadcaster thread drains that queue, sending each message to every client except its sender:
flowchart LR
A[New TCP connection] --> B[Accept Thread]
B -->|spawns one thread per client| C[Receive Thread]
C -->|first message| U[Stored as username]
C -->|later messages| D[(Message Queue<br/>mutex + condvar)]
D --> E[Broadcaster Thread]
E -->|relay to every other client| F[Connected Clients]
Shutdown sequence — shutdown_server sends a sentinel command that the server only accepts from 127.0.0.1:
sequenceDiagram
participant Util as shutdown_server
participant Main as Server main thread
participant Bcast as Broadcaster thread
participant Clients as Connected clients
Util->>Main: connect from 127.0.0.1, send SHUTDOWN_COMMAND
Main->>Main: set shutdown flag, close listening socket
Main->>Bcast: wake broadcaster (condvar)
Bcast->>Bcast: drain any remaining queued messages
Bcast->>Clients: send SHUTDOWN_COMMAND to everyone
Clients->>Clients: print notice, close socket, exit
Clients-->>Main: per-client threads detect closed socket
Main->>Main: wait until all clients disconnected, then exit
Synchronization is split across a few mutex/condition-variable pairs:
| Purpose | Mutex | Condition variable |
|---|---|---|
| Connected-clients list & online count | clients_mutex |
shutdown_server_cond - signaled once the last client disconnects during shutdown |
| Shared message queue | queue_mutex |
queue_cond - wakes the broadcaster when a message is queued, or when shutdown begins |
| Shutdown flag | shutdown_mutex |
- |
On the wire, a broadcast message looks like <bold>username<reset>:message; clients split on the first : to recover the username and the message text. Messages top out at 1024 bytes per line.
MIT — see LICENSE.