As a beginner coder, I’m currently working on creating a chat room application in Java where multiple clients can join. However, I’ve encountered a problem related to updating the list of users displayed in the GUI on the client side.
Here’s what’s happening: When a client joins or leaves the chat, the server sends a message containing a list of clients to all connected clients. I’m trying to update the list of users displayed in the GUI on the client side based on this message. However, I’ve noticed that sometimes the 4th or 5th client’s member list appears empty in their GUI, and occasionally, the list isn’t updated correctly when a new client joins.
I’m considering whether there is a more efficient way to send the list of clients directly from the server and add them to the client’s member list, bypassing the need for the “/members” message entirely.
I’ve implemented a rule to exclude the “/members” list from being displayed in the chat area, as I intend to add the names directly to the member list within the GUI on the client side. I’ve also separated the GUI from the server-side logic to ensure that the GUI is specific to each client.
I’m not sure if this approach is the most effective, and I’m seeking advice on how to resolve these issues and improve the functionality of my application.
Thank you in advance for any assistance you can provide!
Client Side of the code:
package TCP;
import javax.swing.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Base64;
public class Client implements Runnable {
private Socket client;
private BufferedReader in;
private PrintWriter out;
private int port;
private String ip;
private boolean done;
private GuiChatt guiChatt;
public Client(GuiChatt guiChatt, String ip, int port) {
this.guiChatt = guiChatt;
this.ip = ip;
this.port = port;
}
@Override
public void run(){
try {
client = new Socket(ip, port);
out = new PrintWriter(client.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
done = true;
String userName = guiChatt.getUserName();
out.println(userName); //send user join name
InputHandler inHandler = new InputHandler(in);
Thread thread = new Thread(inHandler);
thread.start();
String inMessage;
while (done && (inMessage = in.readLine()) != null) {
guiChatt.addMessage(inMessage);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
shutdown();
}
}
private void shutdown() {
done = true;
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (client != null && !client.isClosed()) {
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendMessageToServer(String message) {
if (out != null) {
System.out.println("(sendMessage to server Client class)Attempting to send message: " + message);
out.println(message);
if (out.checkError()) {
System.out.println("(sendMessage to server Client class) Message not sent: " + message);
} else {
System.out.println("(sendMessage to server Client class) Message sent: " + message);
}
}
}
//server message listener gets messeages from the server that other clients have sent
class InputHandler implements Runnable { //asks constantly for new line inputs
BufferedReader in;
public InputHandler(BufferedReader in) {
this.in = in;
}
@Override
public void run() {
try {
String inMessage;
while (done && (inMessage = in.readLine()) != null) {
if (inMessage.startsWith("/members")) {
// Handle /members message
String[] members = inMessage.substring(9).split(",");
System.out.println("Received members list: " + String.join(", ", members)); // Added logging
SwingUtilities.invokeLater(() -> {
guiChatt.updateMembersList(members);
});
} else if (inMessage.startsWith("/image")) {
// Handle image messages
String imageData = inMessage.substring(7);
byte[] decodedImage = Base64.getDecoder().decode(imageData);
ImageIcon receivedImage = new ImageIcon(decodedImage);
guiChatt.addImage(receivedImage);
} else if (!inMessage.startsWith("/")) { // Skip messages starting with "/"
// Handle regular chat messages
guiChatt.addMessage(inMessage);
}
}
} catch (IOException e) {
e.printStackTrace();
shutdown();
}
}
}
}
```
snippets of the gui. related to the list
private JList<String> membersList;
////
membersListModel = new DefaultListModel<>();
membersList = new JList<>(membersListModel);
membersList.setBackground(new Color(46, 46, 46));
membersList.setForeground(Color.WHITE);
membersList.setFont(new Font("Arial", Font.PLAIN, 14));
JScrollPane membersScrollPane = new JScrollPane(membersList);
membersScrollPane.setBorder(null);
membersScrollPane.setPreferredSize(new Dimension(150, messagesScrollPane.getHeight()));
////
public void updateMembersList(String[] members) {
SwingUtilities.invokeLater(() -> {
membersListModel.clear();
for (String member : members) {
membersListModel.addElement(member);
}
});
}
and my server class:
package TCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server implements Runnable {
private ServerSocket serverSocket;
private int port;
private List<ConnectionHandler> connectionHandlerList; //this broadcast to all clientsList
private ExecutorService pool;
private Set<String> clientsList;
private boolean done;
public Server(int port) {
this.port = port;
connectionHandlerList = new CopyOnWriteArrayList<>();
clientsList = Collections.synchronizedSet(new HashSet<>());
done = false;
}
@Override
public void run(){
try {
serverSocket = new ServerSocket(port);
pool = Executors.newCachedThreadPool(); //maybe add this to the constructor later on for debugging
done = true;
while (done) {
Socket client = serverSocket.accept();
ConnectionHandler handler = new ConnectionHandler(client, this);//for each new client
connectionHandlerList.add(handler);
pool.execute(handler);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
shutdown();
}
}
public void broadcast(String message){
synchronized (connectionHandlerList){
for(ConnectionHandler connectionHandler : connectionHandlerList){
if (connectionHandler != null){
connectionHandler.sendMessage(message);
}
}
}
}
public synchronized void newClient(String client, ConnectionHandler handler){
clientsList.add(client);
broadcast(client + " joined the chat");
updateMembers();
}
public synchronized void leftClient(String client, ConnectionHandler handler) {
clientsList.remove(client);
broadcast(client + " left the chat");
updateMembers();
}
public void updateMembers() {
StringBuilder memberList = new StringBuilder("/members ");
synchronized (clientsList) {
for (String client : clientsList) {
memberList.append(client).append(",");
}
}
if (memberList.length() > 0) {
memberList.setLength(memberList.length() - 1); // Remove the trailing comma
}
System.out.println("Broadcasting members: " + memberList.toString());
broadcast(memberList.toString());
}
public void shutdown() {
done = true;
pool.shutdown();
try {
if (serverSocket != null && !serverSocket.isClosed()) {
serverSocket.close();
}
synchronized (connectionHandlerList) {
for (ConnectionHandler connectionHandler : connectionHandlerList) {
connectionHandler.shutdown();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//the class below is created for each client
class ConnectionHandler implements Runnable {
private Socket client;
private BufferedReader in; //gets the stream from socket, meaning when client send
private PrintWriter out; //writes out to the client
private String userName;
private final Server server;
public ConnectionHandler(Socket client, Server server) {
this.client = client;
this.server = server;
}
@Override
public void run() {
try {
out = new PrintWriter(client.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
//ADD FUNCTIONALITY FOR SENDING AND RECIVNG HERE
userName = in.readLine(); //to get input from streams
server.newClient(userName, this);
//add each client to the list
String message;
while ((message = in.readLine()) != null) {
if (message.startsWith("/image ")) {
server.broadcast("/image " + message.substring(7));
} else {
server.broadcast(userName + ": " + message);
}
}
} catch (IOException e){
e.printStackTrace();
} finally {
//server.leftClient(client, this);
shutdown();
}
}
public void sendMessage(String message) {
out.println(message);
}
public void shutdown() {
try {
if(!client.isClosed()){
client.close();
}
server.leftClient(userName, this);
} catch (IOException e) {
//throw new RuntimeException(e);
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Server server = new Server(12345);
new Thread(server).start();
}
}
I’m unsure whether to include longer code snippets because my application begins with a prompt for users to enter their name, IP address, and port number before joining the chat room. Should I provide this initial prompt code along with the relevant parts of the client and server code that handle communication and user list updates?
I attempted to implement an update function that periodically checks the list of connected clients every 10 seconds and updates the member list accordingly. However, I’m uncertain about its practicality and whether it is synchronized properly. I’ve been stuck on this issue for days and despite searching for a solution, I haven’t been able to find one specifically.
aimz is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.