A web server built from raw sockets in C++, with no frameworks. It started as a single-threaded server that could handle one request at a time, and grew into a multithreaded version backed by a fixed-size thread pool.
The server has two main pieces working together:
- The main thread just listens. It calls
accept()in a loop, and the moment a new client connects, it wraps the work for that client in a lambda and drops it into a task queue — it doesn't do the actual work itself. - A pool of worker threads sit waiting on that queue. Whenever a task shows up, one free worker picks it up, reads the raw HTTP request with
recv(), parses out the method and path by hand (no regex, just.find()and.substr()), matches it against known routes, and sends back the right response.
The first version of this server spun up a brand new std::thread for every single client, and killed it once that client was done. That works, but it's wasteful — creating and destroying threads has real overhead, and a burst of traffic could spin up thousands of short-lived threads at once.
A thread pool fixes this by creating a small, fixed number of worker threads once, up front, and reusing them for every request. New connections get dropped into a queue instead of getting their own thread, and idle workers pick up whatever's waiting.
To actually understand why concurrency needs careful handling, a global requestCount counter was added — incremented once per request, shared across every worker thread.
Without protection, this is unsafe: count++ isn't a single step, it's read → add 1 → write back. If two threads read the same value before either writes back, one increment gets silently lost. Running it casually with a handful of curl requests didn't show any visible corruption — but that's expected. Race conditions are probabilistic, not guaranteed to fail every time, which is exactly why they're dangerous in real systems: they can pass casual testing and still be a real bug waiting to happen under load.
The fix is a mutex — only one thread can hold the lock at a time, so the read-modify-write on requestCount can never be interrupted halfway by another thread.
- Custom thread pool with a fixed number of worker threads
- Thread-safe request counter, protected with a mutex
- Routes
/,/about,/contact— each served from a real HTML file, read once at startup - Proper
404 Not Foundresponse for unmatched routes - Dynamic
Content-Lengthcalculated from the actual response body, not hardcoded
g++ -std=c++17 main.cpp -o server -pthread
./serverThen test it:
curl http://localhost:8080/
curl http://localhost:8080/about
curl http://localhost:8080/contact
curl http://localhost:8080/anything-else