forked from AdvancedProgramming1404/WS-09-Network
main
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
ServerSocketon 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
connectedClientsset is wrapped withCollections.synchronizedSetto ensure thread safety - TODOs:
broadcast(),broadcastSystemMessage(), andremoveClient()methods are incomplete
ClientHandler.java -- Manages a single client
- Implements
Runnableso 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(), andcleanup()are incomplete
MessageListener.java -- Listens for server messages
- Implements
Runnableto 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
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
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
@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
mvn clean compile
Start the Server
java -cp target/classes chat.ChatServer
Start a Client (in a separate terminal)
java -cp target/classes chat.ChatClient
You can also specify a custom port and host:
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)insidebroadcast(). If two threads modify the set concurrently, aConcurrentModificationExceptionwill be thrown. - Resource Cleanup: Always close
Socketand streams in afinallyblock 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)
Languages
Java
100%