I am implementing a Client-Server chatting application where each Client runs as a Thread and two classes ReadThread and WriteThread manage the communication between the multiple Clients and the Server. The application uses Socket connections to read and write data between Clients and the Server.
How it works: The Client will send the Server a String in the form "login:username:password" or "register:username:password". The Server
will read the String through the socket and parse it accordingly using the desired helper method.
My issue: When multiple Clients threads attempt to Login or Register , the Server
only reads the input coming from ONE Client's socket. The other Clients successfully write data to the Server
, but the Server
never reads the data they send.
Below is a minimal running example of the code
First, the Server Class. The ArrayList allClients keeps track of all registered Clients in the application. The purpose of the readThread
object is to read data written to the socket by a Client. This readThread
's run()
method should be executed whenever a Client clicks Login , Register or writes a message on the Server's socket.
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class Server {
private ArrayList<Client> allClients;
public Server() throws IOException {
this.allClients = new ArrayList<>();
}
public static void main(String[] args) throws IOException {
Server server = new Server();
int port = 1234;
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = serverSocket.accept();
//This is the object that I believe is causing the problem
//The run() method in this object should be executed EVERY TIME the
//Client clicks "Login" or "Submit" (submit register data) in the GUI
//But what happens is that this method executes only for ONE Client...
//The other clients successfully write to the socket, but the server never
//reads the data that is written.
//In short: this run() method should read data from the socket for EVERY Client
//but it only reads data from ONE Client
ReadThread readThread = new ReadThread(socket) {
public void run() {
while (true) {
try {
server.parseIncomingText(this.reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
Thread read = new Thread(readThread);
read.start();
}
public void parseIncomingText(String text){
//Do some text processing
}
}
Next we have the Client
class. The client keeps a list of contacts from which they can choose who they want to chat with. The port
used is the same as the Server
's
import gui.Login;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
public class Client extends Thread {
String name;
String username;
String password;
ArrayList<Client> contacts;
boolean isLoggedIn = false;
public Client() {
contacts = new ArrayList<>();
}
@Override
public void run() {
String host = "localhost";
int port = 1234;
Socket socket = null;
try {
socket = new Socket(host, port);
} catch (IOException e) {
e.printStackTrace();
}
// Login.Java is the GUI Class where the user signs
// in or chooses to register
new Login(socket);
}
//Instantiating and running three Client Threads
public static void main(String[] args) {
Client client1 = new Client();
Client client2 = new Client();
Client client3 = new Client();
client1.start();
client2.start();
client3.start();
}
}
Next we have the two classes ReadThread
and WriteThread
which simply manage the I/O between the multiple Client
s and the Server
This is ReadThread
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ReadThread implements Runnable {
Socket socket;
BufferedReader reader;
public ReadThread(Socket socket) {
this.socket = socket;
try {
reader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while (true) {
try {
String response = reader.readLine();
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
And this is WriteThread
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class WriteThread implements Runnable {
Socket socket;
BufferedWriter writer;
Scanner sc;
public BufferedWriter getWriter() {
return writer;
}
public WriteThread(Socket socket) {
sc = new Scanner(System.in);
this.socket = socket;
try {
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
while (true) {
String line = sc.nextLine();
try {
writer.write(line);
writer.newLine();
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Lastly the Login
and Register
GUI classes. This part is crucial to my question as this is where the I/O takes place. I will try to keep the code as minimal as possible.
Here is the Login
GUI class
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.Socket;
public class Login implements ActionListener {
Socket socketBetweenThisClientAndTheServer;
JFrame frame;
JPanel panel;
JLabel labelUsername;
JLabel labelPassword;
JTextField textfieldUsername;
JPasswordField passwordfieldPassword;
JButton buttonLogin;
JButton buttonRegister;
String username;
String password;
public Login(Socket socket) {
socketBetweenThisClientAndTheServer = socket;
frame = new JFrame();
frame.setSize(400, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new JPanel();
panel.setLayout(null);
labelUsername = new JLabel("Username");
labelPassword = new JLabel("Password");
labelUsername.setBounds(10, 20, 80, 25);
labelPassword.setBounds(10, 50, 80, 25);
textfieldUsername = new JTextField(32);
passwordfieldPassword = new JPasswordField();
textfieldUsername.setBounds(100, 20, 165, 25);
passwordfieldPassword.setBounds(100, 50, 165, 25);
buttonLogin = new JButton("Login");
buttonLogin.setBounds(55, 80, 80, 25);
buttonLogin.addActionListener(this);
buttonRegister = new JButton("Register");
buttonRegister.setBounds(145, 80, 120, 25);
buttonRegister.addActionListener(this);
panel.add(buttonRegister);
panel.add(buttonLogin);
panel.add(textfieldUsername);
panel.add(passwordfieldPassword);
panel.add(labelUsername);
panel.add(labelPassword);
frame.add(panel);
frame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == buttonLogin) {
this.username = textfieldUsername.getText();
this.password = passwordfieldPassword.getText();
String textToParse = "login:" + username + ":" + password;
//This is the part where I write a line to the server
//I have debugged and am certain that the message is written
//to the Server's socket successfully
//The issue is that the server only reads the line written by
//by only ONE of the Clients that execute this run() method
WriteThread writeThread = new WriteThread(socketBetweenThisClientAndTheServer) {
public void run() {
try {
this.getWriter().write(textToParse);
this.getWriter().newLine();
this.getWriter().flush();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
Thread write = new Thread(writeThread);
write.start();
} else if (e.getSource() == buttonRegister) {
new Register(socketBetweenThisClientAndTheServer);
}
}
}
And finally the Register
GUI class
import app.WriteThread;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.Socket;
public class Register implements ActionListener {
Socket socketBetweenThisClientAndTheServer;
JFrame frame;
JPanel panel;
JLabel labelUsername;
JLabel labelPassword;
JTextField textfieldUsername;
JPasswordField passwordfieldPassword;
JButton buttonSubmit;
public Register(Socket socket) {
socketBetweenThisClientAndTheServer = socket;
frame = new JFrame();
frame.setSize(400, 200);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
panel = new JPanel();
panel.setLayout(null);
labelUsername = new JLabel("Username");
labelPassword = new JLabel("Password");
labelUsername.setBounds(10, 50, 80, 25);
labelPassword.setBounds(10, 80, 80, 25);
textfieldUsername = new JTextField(32);
passwordfieldPassword = new JPasswordField();
textfieldUsername.setBounds(100, 50, 165, 25);
passwordfieldPassword.setBounds(100, 80, 165, 25);
buttonSubmit = new JButton("Submit");
buttonSubmit.setBounds(165, 110, 80, 25);
buttonSubmit.addActionListener(this);
panel.add(labelUsername);
panel.add(labelPassword);
panel.add(textfieldUsername);
panel.add(passwordfieldPassword);
panel.add(buttonSubmit);
frame.add(panel);
frame.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
String username = textfieldUsername.getText();
String password = passwordfieldPassword.getText();
//This is the String I want to send to the Server for processing
String textToParse = "register:" + username + ":" + password;
//Similar to the object in the Login class's actionPerfored() method.
//The message is successfully written to the Server... but the Server
//Only reads the message sent by ONE of the clients that have executed
//This run() method.
WriteThread writeThread = new WriteThread(socketBetweenThisClientAndTheServer) {
public void run() {
try {
this.getWriter().write(textToParse);
this.getWriter().newLine();
this.getWriter().flush();
} catch (IOException ex) {
ex.printStackTrace();
}
}
};
Thread write = new Thread(writeThread);
write.start();
}
}
How can I get the Server to read messages sent by all the Client threads and not just one Client thread?