forked from AdvancedProgramming1404/WS-09-Network
211 lines
7.4 KiB
Markdown
211 lines
7.4 KiB
Markdown
# 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)
|
|
```
|