Rewrite README in English with full project documentation
This commit is contained in:
@@ -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)
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user