Lab 7: Java Networking with Socket Programming

The goal of this lab is to build a multi-client chat application using Java sockets and multithreading to handle concurrent connections.

In modern networked applications, the ability to handle multiple simultaneous clients is essential. From chat applications and online gaming to distributed systems, the client-server architecture using socket programming forms the backbone of network communication.

In this lab, we will implement a robust multi-client chat server that can efficiently handle multiple concurrent connections.

Objectives

In this lab you will:

  1. Implement a multi-threaded server that:

    • Listens for incoming client connections
    • Creates a new thread for each client
    • Broadcasts messages to all connected clients
    • Handles client disconnections gracefully
  2. Implement a chat client that:

    • Connects to the server
    • Sends messages to the server
    • Receives and displays messages from other clients
    • Handles connection errors and server disconnection
  3. Understand and implement proper resource management:

    • Closing sockets and streams when they are no longer needed
    • Handling exceptions appropriately
    • Implementing a clean shutdown mechanism

Requirements and Tools

  • Java JDK 11 or above
  • Basic understanding of TCP/IP networking
  • Understanding of Java threads and concurrency
  • Knowledge of Java I/O streams

Problem Statement

You are building a simple chat application that allows multiple users to connect and exchange messages in a common chatroom. The server should be able to handle connections from multiple clients simultaneously and broadcast messages to all connected clients. The client should be able to send messages to the server and display messages received from other clients.

Getting Started

If your instructor is using GitHub classroom, you will need to accept the assignment using the link at the bottom of this page, clone your auto-generated repository, and import it as a project into your IDE.

If your instructor is not using GitHub classroom, clone and import the template project at https://github.com/cpit305-spring-25-IT1/lab-07 ↗.

Part 1: Chat Server Implementation

In this part, you will implement the server-side components of the chat application.

Task 1.1: Create a Message Class

First, create a ChatMessage class to represent the messages exchanged between clients and the server. This class will handle both plain text messages and special command messages, such as connect, disconnect, and list users. The class should keep track of the sender, timestamp, content, and the message type (connect, disconnect, list users, and message.) This class should be serializable so that it can be easily transmitted over the network.

Complete the ChatMessage class at src/main/java/cpit305/fcit/kau/edu/sa/sockets/ChatMessage.java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ChatMessage implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String sender;
    private String content;
    private LocalDateTime timestamp;
    private MessageType type;
    
    public enum MessageType {
        CONNECT,
        DISCONNECT,
        MESSAGE,
        USER_LIST
    }
    
    // Add constructors, getters, setters, and toString method
    
}

Task 1.2: Create a Client Handler

Next, implement a ClientHandler class that will manage communication with each connected client. This class should run in a separate thread for each client by either extending Thread or implementing the Runnable interface.

Complete the ClientHandler class at src/main/java/cpit305/fcit/kau/edu/sa/sockets/ClientHandler.java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class ClientHandler implements Runnable {
    private final Socket clientSocket;
    private final ChatServer server;
    private ObjectInputStream inputStream;
    private ObjectOutputStream outputStream;
    private String clientName;
    private boolean isRunning;
    
    public ClientHandler(Socket socket, ChatServer server) {
        this.clientSocket = socket;
        this.server = server;
        this.isRunning = true;
    }
    
    @Override
    public void run() {
        try {
            // Initialize I/O streams
            // We should create the output stream before the input stream to avoid deadlocks
            outputStream = new ObjectOutputStream(clientSocket.getOutputStream());
            inputStream = new ObjectInputStream(clientSocket.getInputStream());
            
            
            // Get client's name from the initial connection message (type CONNECT)
            ChatMessage chatMessage = (ChatMessage) inputStream.readObject();
            if (chatMessage.getType() == ChatMessage.MessageType.CONNECT) {
                this.clientName = chatMessage.getSender();
                server.broadcastMessage(chatMessage, this);
            }
            // Handle messages in a loop
            while (isRunning){
                ChatMessage message = (ChatMessage)  inputStream.readObject();
                switch (message.getType()) {
                    case ChatMessage.MessageType.DISCONNECT:
                        isRunning = false;
                        server.broadcastMessage(message, this)
                        break;
                    case ChatMessage.MessageType.USER_LIST:
                       // list all users and send it to this client using sendMessage()
                       break;
                    case ChatMessage.MessageType.MESSAGE:
                      // ask the server to broadcast the message to all connected clients
                      break;

                }
            }
            
        } catch (IOException | ClassNotFoundException e) {
            System.err.println("Error with client connection: " + e.getMessage());
        } finally {
            // Clean up resources when the client disconnects
            closeConnection();
        }
    }
    
    public void sendMessage(ChatMessage message) {
        // Send a message to this client using its OutputStream
    }
    
    private void closeConnection() {
        // Close all streams and socket, notify server about disconnection
    }

    public String getClientName() {
        return clientName;
    }
}

Task 1.3: Implement the Chat Server

Next, implement the ChatServer class that will listen for client connections and manage a list of connected clients.

Implement the ChatServer class at src/main/java/cpit305/fcit/kau/edu/sa/sockets/ChatServer.java.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class ChatServer {
    private final int port;
    private final List<ClientHandler> clients = new ArrayList<>();
    private ServerSocket serverSocket;
    private boolean isRunning;
    
    public ChatServer(int port) {
        this.port = port;
        this.isRunning = false;
    }
    
    public void start() {
        try {
            serverSocket = new ServerSocket(port);
            isRunning = true;
            System.out.println("Server started on port " + port);
            
            // Accept client connections in a loop
            while (isRunning) {
                try {
                    Socket clientSocket = serverSocket.accept();
                    System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
                    
                    // Create a new handler for this client
                    ClientHandler clientHandler = new ClientHandler(clientSocket, this);
                    clients.add(clientHandler);
                    
                    // Start the client handler in a new thread
                    
                    
                } catch (IOException e) {
                    if (isRunning) {
                        System.err.println("Error accepting client connection: " + e.getMessage());
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("Server startup error: " + e.getMessage());
        }
    }
    
    public synchronized void broadcastMessage(ChatMessage message, ClientHandler sender) {
        // Send the message to all connected clients except the sender
    }
    
    public synchronized void removeClient(ClientHandler client) {
        // Remove a client from the list and notify other clients
    }
    
    public synchronized List<String> getClientNames() {
        // Return a list of all connected client names
        return null;
    }
    
    public void stop() {
        // Gracefully shutdown the server
    }
    
    // Main method to start the server
    public static void main(String[] args) {
        int port = 9654;    
        ChatServer server = new ChatServer(port);
        server.start();
    }
}

Part 2: Chat Client Implementation

Now, we will implement the client-side components of the chat application.

Task 2.1: Implement the Chat Client

Complete the ChatClient class to manage communication with the server and exchange messages with the connected clients.

Complete the ChatClient class at src/main/java/cpit305/fcit/kau/edu/sa/sockets/ChatClient.java.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class ChatClient {
    private final String serverAddress;
    private final int serverPort;
    private final String username;
    
    private Socket socket;
    private ObjectOutputStream outputStream;
    private ObjectInputStream inputStream;
    private boolean isRunning;
    private Scanner scanner;
    
    public ChatClient(String serverAddress, int serverPort, String username) {
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;
        this.username = username;
        this.isRunning = false;
        this.scanner = new Scanner(System.in);
    }
    
    public void start() {
        try {
            // Connect to the server
            socket = new Socket(serverAddress, serverPort);
            isRunning = true;
            
            // Initialize I/O streams
            // We should create the output stream before the input stream to avoid deadlocks
            outputStream = new ObjectOutputStream(socket.getOutputStream());
            inputStream = new ObjectInputStream(socket.getInputStream());
           
            // Send initial connect message with username
            ChatMessage connectMessage = new ChatMessage(username, "has joined the chat", LocalDateTime.now(), ChatMessage.MessageType.CONNECT);
            outputStream.writeObject(connectMessage);
            
            // Start a thread to listen for incoming messages
            startMessageListener();
            
            // Start the user input loop
            startUserInputLoop();
            
        } catch (IOException e) {
            System.err.println("Error connecting to server: " + e.getMessage());
        } finally {
            closeConnection();
        }
    }
    
    private void startMessageListener() {
        // Create and start a thread to listen for messages from the server
        Thread th = new Thread (()-> {
            try {
                while (isRunning) {
                    ChatMessage message = (ChatMessage) inputStream.readObject();
                    // Print received message
                    switch (message.getType()){
                        case USER_LIST:
                            // print the list of users
                            break;
                        case MESSAGE:
                            // print the message, sender, and other info
                            break;
                        case CONNECT:
                            // print connected
                            break;
                        case DISCONNECT:
                            // print disconnected
                            break;
                    }
                }
            }
            catch (IOException | ClassNotFoundException e) {
                if (isRunning) {
                    System.err.println("Connection to server lost: " + e.getMessage());
                    closeConnection();
                }
            }
        });
        th.start();
    }
    
    private void startUserInputLoop() {
        System.out.println("Enter messages (type '/quit' to exit, '/users' to list users):");
        
        while (isRunning) {
            String input = scanner.nextLine().trim();
            
            if (input.isEmpty()) {
                continue;
            }
            
            try {
                if (input.equals("/quit")) {
                    ChatMessage disconnectMessage = new ChatMessage(
                        username,
                        "has left the chat",
                        LocalDateTime.now(),
                        ChatMessage.MessageType.DISCONNECT
                    );
                    outputStream.writeObject(disconnectMessage);
                    isRunning = false;
                } else if (input.equals("/users")) {
                    ChatMessage listMessage = new ChatMessage(
                        username,
                        "",
                        LocalDateTime.now(),
                        ChatMessage.MessageType.USER_LIST
                    );
                    outputStream.writeObject(listMessage);
                } else {
                    ChatMessage chatMessage = new ChatMessage(
                        username,
                        input,
                        LocalDateTime.now(),
                        ChatMessage.MessageType.MESSAGE
                    );
                    outputStream.writeObject(chatMessage);
                }
                outputStream.flush();
            } catch (IOException e) {
                System.err.println("Error sending message: " + e.getMessage());
                closeConnection();
                break;
            }
        }
    }
    
    private void closeConnection() {
        // Clean up resources when the client disconnects
        isRunning = false;
        try {
            // Close all non null I/O resources (outputstream, inputstream, socket, and scanner)




        } catch (IOException e) {
            System.err.println("Error closing connection: " + e.getMessage());
        }
        System.out.println("Disconnected from server");
        System.exit(0);

    }
    
    // Main method to start the client
    public static void main(String[] args) {
        String serverAddress = "localhost";
        int serverPort = 9654;
        String username = "User_" + (int) (Math.random() * 1000);
        
        ChatClient client = new ChatClient(serverAddress, serverPort, username);
        client.start();
    }
}

Part 3: Testing and Evaluation

Task 3.1: Test Your Chat Application

Once you have implemented both the server and client components, test your application by following these steps:

  • Start the server by running the ChatServer class.
  • Start multiple instances of the ChatClient class with different usernames.
  • Send messages from each client and verify that they are received by all other clients.
  • Test disconnection and reconnection scenarios.
  • Refer to the lecture note on networking and:
    • Connect to the chat server via Telnet
    • Make the chat server public with ngrock

Task 3.2: Evaluate Your Implementation

Run the included unit tests under the src/test/java/cpit305/fcit/kau/edu/sa/ package.

Deliverables and Submission

Please push your code to GitHub for auto-grading and submit a PDF file with:

  1. Screenshots showing your implementation