C++ Simple Chat App (Console) Project
Two terminals on one network, sending messages to each other in real time.
What you will build
A chat program where one side runs as the server and the other connects as a client, then messages flow both ways at once — which means one thread must listen while the main thread types. It's a first real taste of network programming and concurrency together.
What you will learn
- TCP sockets: socket, bind, listen, accept, connect
- Running send and receive concurrently with std::thread
- Designing a tiny message protocol
- Clean shutdown when the other side disconnects
Feature checklist
- Server mode and client mode in one program
- Two-way messaging in real time
- Usernames shown with each message
- Handles the other side quitting
- '/quit' command to leave cleanly
Starter code
// POSIX sockets (Linux/macOS). Server side sketch:
#include <iostream>
#include <string>
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
void receiveLoop(int sock) {
char buf[1024];
ssize_t n;
while ((n = recv(sock, buf, sizeof(buf) - 1, 0)) > 0) {
buf[n] = '\0';
std::cout << "\nPeer: " << buf << "\nYou: " << std::flush;
}
std::cout << "\nPeer disconnected.\n";
std::exit(0);
}
int main() {
int server = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{AF_INET, htons(54000), INADDR_ANY};
bind(server, (sockaddr*)&addr, sizeof(addr));
listen(server, 1);
std::cout << "Waiting for a friend on port 54000...\n";
int client = accept(server, nullptr, nullptr);
std::thread t(receiveLoop, client);
t.detach();
std::string line;
std::cout << "You: ";
while (std::getline(std::cin, line))
send(client, line.c_str(), line.size(), 0);
}Full walkthrough
Step 1: Understand the shape: one program, two modes
Run with 'server' and it listens; run with 'client <ip>' and it connects. Both modes then do the same thing — send what you type, print what arrives — which is why the receive logic is shared. If the two-terminal demo works on one machine (127.0.0.1), the networking is right.
Step 2: Create and bind the server socket
socket(AF_INET, SOCK_STREAM, 0) creates a TCP endpoint. Fill a sockaddr_in with INADDR_ANY and htons(5555), bind() it, listen(), then accept() — accept BLOCKS until a client connects, and returns a NEW socket for that client. The listening socket and the client socket are different things; confusing them is the classic first-week bug.
Step 3: Connect from the client
The client creates its socket and connect()s to the server's address (127.0.0.1 for the demo, a real IP over a network). From here both sides hold one socket each and the code can be symmetric — which is exactly why the shared receive loop works.
Step 4: Run receiving on its own thread
Receiving blocks, and typing blocks; one thread cannot wait for both. So receiveLoop(int sock) runs in a std::thread: it recv()s into a buffer and prints 'name: message' lines until recv returns 0 (the other side quit), while main() loops on std::getline sending your lines with send(). This two-thread split is THE pattern of every chat program.
void receiveLoop(int sock) {
char buf[1024];
while (true) {
int n = recv(sock, buf, sizeof(buf) - 1, 0);
if (n <= 0) { std::cout << "\n(peer left)\n"; break; }
buf[n] = '\0';
std::cout << buf << std::endl;
}
}Step 5: Prefix messages with the username
On connect, each side sends its name once; every chat line is sent as 'name: text' (or the receiver prepends the known peer name). In a two-person chat the name is cosmetic — in the advanced multi-client version it becomes essential routing information.
Step 6: Handle quitting cleanly
'/quit' breaks the send loop, then close(sock) and join the receive thread. On the receiving side, recv() returning 0 means an orderly shutdown — print '(peer left)' and exit rather than spinning on empty reads. A chat that requires Ctrl+C to leave will also leave sockets and threads dangling.
Step 7: Test with two terminals on one machine
Terminal 1: ./chat server. Terminal 2: ./chat client 127.0.0.1. Type both ways, confirm usernames, then /quit from one side and watch the other announce the departure. Only after localhost works should you try a real second machine — most 'network bugs' are firewall and IP issues, not code.
Common mistakes
- Trying to receive and send on one thread, so you cannot see replies until you press Enter (both sides are blocked on different waits).
- Assuming one recv() equals one message — TCP is a byte stream; messages can arrive split or glued together. Beginners get away with line-based sends; know that it is a simplification.
- Forgetting htons() on the port, so server and client listen on byte-swapped different ports and never meet.
Compile and run
g++ -std=c++17 -o chat chat.cpp -pthreadPOSIX sockets: this builds on Linux and macOS as-is. On Windows, sockets live in Winsock (winsock2.h, link -lws2_32) and the calls differ — most learners build this one in WSL or a Linux VM. The -pthread flag is needed for the std::thread receive loop.
Sample run
$ ./chat server
Server listening on port 5555...
Client connected.
ada: hello, can you see this?
> yes! loud and clear
ada: the two-thread receive loop works
ada: try /quit when ready
> /quit
(you left the chat)
——— second terminal ———
$ ./chat client 127.0.0.1
Connected to server. Your name:
> ada
you: yes! loud and clear
ada: the two-thread receive loop works
(peer left)Try it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.