Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 Multithreaded Web Server — Java

A hands-on educational project that demonstrates three progressively advanced concurrency models for building TCP servers in Java — moving from a naive single-threaded server, to a thread-per-request model, and finally to a production-style thread-pool architecture.


📖 Table of Contents


Overview

This repository explores the evolution of server concurrency in Java through three self-contained implementations:

Approach Concurrency Model Scalability Resource Usage
Single-Threaded Sequential — one client at a time ❌ Poor ✅ Minimal
Multithreaded Thread-per-request ⚠️ Moderate ❌ High (unbounded threads)
Thread Pool Fixed-size ExecutorService pool ✅ Good ✅ Controlled

Each module is fully independent and can be compiled and run on its own.


Architecture

MultithreadedWebServer/
├── SingleThreaded/
│   ├── Server.java          # Blocking, sequential server
│   └── Client.java          # Single-request client
├── Multithreaded/
│   ├── Server.java          # Thread-per-request server
│   └── Client.java          # Multi-threaded client (100 concurrent requests)
├── ThreadPool/
│   └── Server.java          # Fixed thread-pool server (ExecutorService)
└── README.md

Modules

1. SingleThreaded

A basic TCP server that accepts and processes one connection at a time. While one client is being served, all other clients must wait in the connection queue.

Key characteristics:

  • Listens on port 8010
  • 20-second socket timeout
  • Processes clients sequentially in the main thread
  • Simple PrintWriter / BufferedReader I/O

⚠️ Limitation: This model cannot handle concurrent clients. If a client takes a long time to process, it blocks all subsequent connections.


2. Multithreaded

An improved server that spawns a new thread for every incoming connection, allowing multiple clients to be served simultaneously.

Key characteristics:

  • Listens on port 8010
  • 70-second socket timeout
  • Creates a new Thread per client via lambda + Consumer<Socket>
  • Ships with a load-testing client that fires 100 concurrent threads

⚠️ Limitation: Unbounded thread creation — under heavy load this can exhaust system resources (memory, file descriptors) and crash the JVM.


3. ThreadPool

The most robust implementation, using Java's ExecutorService with a fixed thread pool to limit concurrent thread usage while still handling multiple clients.

Key characteristics:

  • Listens on port 8010
  • 70-second socket timeout
  • Uses Executors.newFixedThreadPool(10) (configurable pool size)
  • Graceful shutdown of the thread pool via shutdown() in a finally block

Production-ready pattern: Thread pools prevent resource exhaustion and provide predictable performance under load.


Getting Started

Prerequisites

  • Java JDK 8+ (uses lambdas, Consumer<T>, ExecutorService)
  • A terminal / command prompt

Compilation & Execution

Each module can be compiled and run independently. Navigate to the desired module directory and follow the steps below.

Single-Threaded Server

# Terminal 1 — Start the server
cd SingleThreaded
javac Server.java
java Server

# Terminal 2 — Start the client
cd SingleThreaded
javac Client.java
java Client

Multithreaded Server

# Terminal 1 — Start the server
cd Multithreaded
javac Server.java
java Server

# Terminal 2 — Start the client (spawns 100 concurrent connections)
cd Multithreaded
javac Client.java
java Client

Thread Pool Server

# Terminal 1 — Start the server
cd ThreadPool
javac Server.java
java Server

# Terminal 2 — Use the Multithreaded Client to test
cd Multithreaded
javac Client.java
java Client

Tip: You can also test any server with telnet localhost 8010 or curl telnet://localhost:8010.


How It Works

┌────────┐         ┌────────────┐
│ Client │───TCP──▶│   Server   │
│        │◀────────│ (port 8010)│
└────────┘  "Hello └────────────┘
          from server"

SingleThreaded:  Main thread handles each client sequentially
Multithreaded:   New Thread() per accept() — unbounded
ThreadPool:      ExecutorService.execute() — bounded pool of N threads
  1. The Server binds to port 8010 and enters an infinite accept() loop.
  2. When a Client connects, the server sends a greeting: "Hello from server <address>".
  3. The Client reads the response and prints it to stdout.
  4. The connection is closed, and the server is ready for the next client.

The key difference between modules is how step 2 is dispatched:

Module Dispatch Mechanism
SingleThreaded Inline in main thread
Multithreaded new Thread(() -> ...).start()
ThreadPool threadPool.execute(() -> ...)

Performance Comparison

Below is a qualitative comparison when tested with the 100-connection client:

Metric SingleThreaded Multithreaded ThreadPool (size=10)
Throughput ~1 req/s High (all at once) High (batched)
Latency (per client) High (queued) Low Low-Medium
Memory usage Constant Spikes (100 threads) Bounded
Thread safety N/A Risk of exhaustion Safe
Recommended for Learning only Small demos Production use

Contributing

Contributions are welcome! Here are some ideas for enhancements:

  • Add HTTP request parsing (GET, POST)
  • Implement a static file server
  • Add logging with java.util.logging
  • Create benchmarking scripts with JMH
  • Add NIO (non-blocking I/O) implementation as a fourth module
  • Dockerize the servers

To contribute:

  1. Fork this repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -m "Add my feature")
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

License

This project is open source and available under the MIT License.


Made with ☕ and Java  |  ⭐ Star this repo if you found it useful!

About

A high-concurrency HTTP/1.1 web server built from scratch in Java using Socket API and thread pools.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages