A multi-threaded file storage server implementing concurrent client handling, worker thread pools, and per-file locking for safe concurrent operations.
- Main Thread: Accepts TCP connections and queues client sockets
- Client Thread Pool: Handles authentication and command parsing (4 threads)
- Worker Thread Pool: Executes file I/O operations (4 threads)
- Communication: Client threads wait on condition variables; workers signal completion
- Client Queue: Capacity 256 (accepts incoming connections)
- Task Queue: Capacity 1024 (file operation requests)
- Both use mutex + condition variables (no busy-waiting)
- Per-session result delivery via condition variables
- Per-file mutex locks with reference counting
- Global user authentication mutex
- Clean shutdown with queue closure and thread joining
- GCC or Clang compiler
- POSIX-compliant system (Linux/macOS)
- pthread library
# Build server and client
make all
# Build with ThreadSanitizer (for race detection)
make tsan
# Clean build artifacts
make cleanserver- Main file serverclient_app- Interactive test clientserver_tsan- Server with ThreadSanitizer instrumentation (viamake tsan)
./serverDefault Configuration:
- Port:
8080 - Storage Directory:
server_storage/(created automatically) - User Database:
server_storage/users.txt
The server will print:
Server listening on 8080
Press Ctrl+C to gracefully shutdown.
./client_appThe client connects to 127.0.0.1:8080 and provides an interactive prompt.
> SIGNUP alice password123
OK signup
> LOGIN alice password123
OK login
> UPLOAD testfile.txt
# Client will read local file and send contents
OK upload
- File must exist locally
- Client auto-detects file size
- Server stores in
server_storage/<username>/
> DOWNLOAD testfile.txt
Downloaded testfile.txt (1234 bytes)
- Saves to current directory
- Overwrites existing file
> LIST
OK list 128
testfile.txt 1234
photo.jpg 56789
- Shows filename and size in bytes
> DELETE testfile.txt
OK delete
> QUIT
OK bye
Terminal 1 (Server):
./serverTerminal 2 (Client):
./client_app
# Run command sequence# Single client flow test
cd tests
./test_single_client.sh
# Concurrent clients test
./test_concurrent.sh# Run server under Valgrind
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./server
# In another terminal, run client operations then Ctrl+C serverExpected output:
All heap blocks were freed -- no leaks are possible
# Build with TSAN
make tsan
# Run instrumented server
./server_tsan
# In another terminal, run concurrent clients
# Check for race warnings in server_tsan outputExpected output: No race condition warnings
Commands are newline-terminated (\n). Binary data follows specific commands.
C: SIGNUP <username> <password>\n
S: OK signup\n OR ERR userexists\n
C: LOGIN <username> <password>\n
S: OK login\n OR ERR badcreds\n
UPLOAD:
C: UPLOAD <filename> <size_in_bytes>\n
S: READY\n
C: <raw binary data of exact size>
S: OK upload\n OR ERR upload <reason>\n
DOWNLOAD:
C: DOWNLOAD <filename>\n
S: OK download <size>\n<raw binary data>
OR ERR download notfound\n
LIST:
C: LIST\n
S: OK list <payload_size>\n<filename1 size1\nfilename2 size2\n...>
OR ERR list <reason>\n
DELETE:
C: DELETE <filename>\n
S: OK delete\n OR ERR delete <reason>\n
server_storage/
├── users.txt # User credentials (username password pairs)
└── <username>/ # Per-user directory
├── file1.txt
├── file2.jpg
└── ...
Files are written to .tmp files and atomically renamed to prevent corruption on crashes.
Edit include/dropbox.h to modify:
#define SERVER_PORT 8080 // TCP port
#define CLIENT_QUEUE_CAP 256 // Max pending connections
#define TASK_QUEUE_CAP 1024 // Max pending tasks
#define CLIENT_POOL_SIZE 4 // Client thread count
#define WORKER_POOL_SIZE 4 // Worker thread countRecompile after changes: make clean && make
- Method: Per-session result slot + condition variable
- Rationale:
- Direct pointer to session (no lookups)
- Only client threads perform socket I/O (worker-safe)
- Low contention (per-session locks, not global)
- Deterministic task routing
- Per-file mutex (not global lock)
- Reference-counted lock entries
- Lock acquired order:
file_map_mutex→file_lock→ perform I/O - Allows concurrent operations on different files
- Current: Single result slot per session (sequential task processing)
- Alternative: Per-session result queue (multiple outstanding tasks)
- Would increase memory usage
- Added complexity in result matching
- Current design sufficient for Phase 1
- All
pthread_*return values checked - Socket I/O uses robust helpers (
read_n_bytes,send_all) - Partial reads/writes handled with loops
- Client disconnect detection (session
aliveflag) - Worker checks session validity before result delivery
- No user quota enforcement (can be added in Phase 2)
- Plain-text password storage (use hashing in production)
- No TLS/SSL encryption
- Single server instance (no horizontal scaling)
# Find process using port 8080
lsof -i :8080
# Kill it or change SERVER_PORT in dropbox.hchmod 755 server_storage- Ensure server is running:
ps aux | grep server - Check firewall settings
- Verify port in client matches server
- ✅ Source code with proper file structure
- ✅ Makefile with all targets
- ✅ README (this file)
- ✅ Automated test scripts
- ✅ Valgrind clean run
- ✅ ThreadSanitizer clean run
Muhammad Wasif BSCS23020 Mobeen Qamar BSCS23050 Saifullah Arshad BSCS23065
Operating Systems Lab - Phase 1
Educational use only.