diff --git a/README.md b/README.md index e69de29..32c377a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,210 @@ +# Chat App + +A simple chat application based on the Client-Server architecture using Java Sockets. This project is intentionally left incomplete to help you learn networking and multi-threading concepts hands-on. + +--- + +## Architecture + +``` ++-------------------+ +-------------------+ +| ChatClient | <------> | ChatServer | +| (Client-Side) | TCP | (Server-Side) | ++-------------------+ +-------------------+ + | + +--------+--------+ + | ClientHandler | + | (Per Client) | + +-----------------+ +``` + +The project consists of two main parts: + +### 1. Server-Side + +**ChatServer.java** -- The core server +- Creates a `ServerSocket` on a specified port and waits for client connections +- Wraps each new client in a `ClientHandler` +- Uses an `ExecutorService` (Thread Pool) to manage clients concurrently +- The `connectedClients` set is wrapped with `Collections.synchronizedSet` to ensure thread safety +- **TODOs**: `broadcast()`, `broadcastSystemMessage()`, and `removeClient()` methods are incomplete + +**ClientHandler.java** -- Manages a single client +- Implements `Runnable` so each client runs in its own thread +- Requests a username upon connection +- Forwards received messages to the server for broadcasting +- Provides `sendMessage()` to send messages back to this specific client + +### 2. Client-Side + +**ChatClient.java** -- The user-facing client +- Connects to the server and starts a separate thread for receiving messages +- Reads console input and sends it to the server +- **TODOs**: `connectToServer()`, `readUserInputAndSend()`, and `cleanup()` are incomplete + +**MessageListener.java** -- Listens for server messages +- Implements `Runnable` to run in a separate thread +- Continuously prints messages received from the server +- **TODO**: `run()` method is incomplete + +--- + +## Concepts You Will Learn + +| Concept | Description | Why It Matters | +|---------|-------------|----------------| +| **Socket Programming** | Communication between programs over a network using `ServerSocket` and `Socket` at the TCP level | Foundation of all real-world network communication (web, chat, online games) | +| **Multi-threading** | Running multiple tasks concurrently with `Thread`, `Runnable`, and `ExecutorService` | Without it, one slow client could block the entire server | +| **Thread Pool** | Using `Executors.newCachedThreadPool()` for efficient thread management | Creating a new thread per client is expensive; Thread Pools recycle threads | +| **I/O Streams** | Reading and writing data with `BufferedReader` and `PrintWriter` over the network | Learn how data is transferred between different systems | +| **Synchronized Collections** | Making shared collections thread-safe with `Collections.synchronizedSet()` | When multiple threads access a collection without coordination, data gets corrupted | +| **Daemon Thread** | A thread that automatically terminates when the program exits via `setDaemon(true)` | No need to manually close it; prevents thread leaks | +| **Client-Server Architecture** | Separating server and client logic into distinct classes | Learn how distributed systems are designed | + +--- + +## Completing the Project -- TODOs + +Parts of the code are marked with `TODO` and need to be completed: + +### ChatServer.java + +```java +public void broadcast(String message, ClientHandler sender) { + synchronized (connectedClients) { + for (ClientHandler client : connectedClients) { + if (client != sender) { + client.sendMessage(message); + } + } + } +} + +public void broadcastSystemMessage(String message) { + synchronized (connectedClients) { + for (ClientHandler client : connectedClients) { + client.sendMessage(message); + } + } +} + +public void removeClient(ClientHandler clientHandler) { + connectedClients.remove(clientHandler); + broadcastSystemMessage("[System] " + clientHandler.getUsername() + " left the chat."); +} +``` + +### ChatClient.java + +```java +private void connectToServer() throws IOException { + socket = new Socket(host, port); + out = new PrintWriter(socket.getOutputStream(), true); + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); +} + +private void readUserInputAndSend() throws IOException { + String input; + while ((input = consoleReader.readLine()) != null) { + out.println(input); + } +} + +private void cleanup() { + try { + if (socket != null && !socket.isClosed()) { + socket.close(); + } + } catch (IOException e) { + System.err.println("Error closing socket: " + e.getMessage()); + } +} +``` + +### MessageListener.java + +```java +@Override +public void run() { + try { + String message; + while ((message = inputReader.readLine()) != null) { + System.out.println(message); + } + } catch (IOException e) { + System.err.println("Connection lost: " + e.getMessage()); + } +} +``` + +--- + +## How to Run + +### Prerequisites +- Java 21+ +- Maven + +### Compile +```bash +mvn clean compile +``` + +### Start the Server +```bash +java -cp target/classes chat.ChatServer +``` + +### Start a Client (in a separate terminal) +```bash +java -cp target/classes chat.ChatClient +``` + +You can also specify a custom port and host: +```bash +java -cp target/classes chat.ChatServer 12345 +java -cp target/classes chat.ChatClient 127.0.0.1 12345 +``` + +--- + +## Important Notes + +- **Synchronization**: Always use `synchronized (connectedClients)` inside `broadcast()`. If two threads modify the set concurrently, a `ConcurrentModificationException` will be thrown. +- **Resource Cleanup**: Always close `Socket` and streams in a `finally` block or use try-with-resources to avoid resource leaks. +- **Thread Naming**: Use `setName()` on threads for easier debugging. +- **Port Selection**: Ports below 1024 are reserved by the OS. Use higher ports (e.g., 12345). +- **Local Testing**: Server and client can be tested on the same machine using `127.0.0.1`. + +--- + +## Common Mistakes to Understand + +| Mistake | Explanation | +|---------|-------------| +| **Not using synchronized in broadcast** | Causes `ConcurrentModificationException` or lost messages | +| **Not closing streams in finally** | If an exception occurs, the port remains occupied by the OS for a long time | +| **Forgetting to skip the sender in broadcast** | The sender receives their own message back (echo) | +| **Using new Thread instead of ExecutorService** | Creates a new thread per client, which is expensive and unmanageable | +| **Not reading continuously from inputReader in MessageListener** | If the listener thread sleeps, server messages never reach the user | +| **Not using setDaemon(true)** | When the client exits, the listener thread keeps running and the program won't terminate | +| **Ignoring null from readLine()** | When the connection is closed, `readLine()` returns `null`; without checking, the loop runs forever | + +--- + +## Project Structure + +``` +chat-app/ +├── pom.xml +├── README.md +├── src/ +│ └── main/ +│ └── java/ +│ └── chat/ +│ ├── ChatServer.java # The chat server +│ ├── ClientHandler.java # Manages a single client +│ ├── ChatClient.java # The chat client +│ └── MessageListener.java # Listens for incoming messages +└── target/ # (generated by Maven -- in .gitignore) +```