# 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) ```