Rewrite README in English with full project documentation

This commit is contained in:
MobinAbedian
2026-06-07 15:13:42 +03:30
parent 1c115d5f88
commit 3ebd5717ed
+210
View File
@@ -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)
```