I'm trying to create a text chat with Java. I have a Server and a Client that connect to each other using Streams, and send data using the objectInputStream and objectOutputStream.
I have GUI's for both the client and the server.
I made these GUI's using intellij's GUI Form.
server GUI form image
The problem I'm having is when I try to display text to the GUI of the server. I can append to the GUi if I call my relayToAll method from the JTextField actionlistener, which then send the message to all the clients and prints it out in the servers GUI.
If i try to call the same method from where I receive the input, then the append to the text area does not work.
Can anyone tell me why its not appending?
Thanks
public class ServerTest {
private JTextField textField1;
private JTextArea textArea1;
private JPanel Panel;
static private ObjectOutputStream objectOutputStream;
static private ObjectInputStream objectInputStream;
static private Socket client;
static private ArrayList<Socket> clients = new ArrayList<Socket>();
static private ArrayList<ObjectOutputStream> objectOutputStreams = new ArrayList<>();
public void relayToAll(String message){
try {
for(int i = 0; i < clients.size(); i++) {
ObjectOutputStream output = objectOutputStreams.get(i);
output.writeObject(message);
output.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
appendTextArea(message);
}
public void appendTextArea(String text){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.out.println("This should go to the Server GUI: " + text);
textArea1.append(text + "\n");
}
});
}
public ServerTest() {
textField1.addActionListener(e -> {
System.out.println(e.getActionCommand());
relayToAll(e.getActionCommand());
textField1.setText("");
});
}
public void ReadInput(ObjectInputStream input, int port){
try {
String oldMessage = "";
while (true) {
String message = (String) input.readObject();
if (message != oldMessage){
System.out.println(port + ": " + message);
oldMessage = message;
relayToAll(port + ": " + message);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void IOSetup(){
try {
ServerSocket serverSocket = new ServerSocket( 6969 );
ExecutorService executor = Executors.newFixedThreadPool(5);
System.out.println("server on\n");
for (int i = 0; i < 5; i++){
client = serverSocket.accept();
clients.add(client);
System.out.println("Connection from: "+ client.getPort());
objectOutputStream = new ObjectOutputStream(client.getOutputStream());
objectOutputStreams.add(objectOutputStream);
objectInputStream = new ObjectInputStream(clients.get(i).getInputStream());
executor.submit(() -> {
ReadInput(objectInputStream, client.getPort());
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Server");
frame.setContentPane(new ServerTest().Panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
ServerTest application = new ServerTest();
application.IOSetup();
}
Actually you've got kind of a silly mistake. Please check lines (A) and (B) below:
public static void main(String[] args) {
JFrame frame = new JFrame("Server");
frame.setContentPane(new ServerTest().Panel); // *************** (A)
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
ServerTest application = new ServerTest(); // *************** (B)
application.IOSetup();
}
Do you see a problem? You're creating TWO ServerTest objects, one that has its Panel variable added to the JFrame and that gets displayed, and the other that is set up for IO communication. The ActionListener changes the state of the displayed JTextArea while the IO communications changes the state of a JTextArea that is in the second ServerTest instance, the one not displayed.
One improvement is to create only one instance:
public static void main(String[] args) {
ServerTest application = new ServerTest(); // create one instance
JFrame frame = new JFrame("Server");
// frame.setContentPane(new ServerTest().Panel);
frame.setContentPane(application.Panel); // and use in both places
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
//ServerTest application = new ServerTest();
application.IOSetup(); // and use in both places
}
Other problems:
You've got long-running code long running and blocking in a background thread, and that is potentially dangerous, and the only reason that your GUI is not getting frozen is because you're starting the GUI (incorrectly) on the main thread and off of the Swing event thread. For more on this, you will want to read up on Swing concurrency: Lesson: Concurrency in Swing
You will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.
Related
I wanted to make a chat server client with GUI. Any new messages will be added as a JPanel. Initially messages added to my JScrollPanel is updating smoothly. However when i implemented the server and client to work with the GUI, the first few new messages added are never updated. Messages will only be updated to the JScrollPanel after the third add onward. Some times the adding of components ended prematurely. The client implements runnable so any new messages will be updated to the JScrollPanel via a Thread.
It seems like the GUI did not fully initialise.
this is the Client code
public static void main(String[] args) {
new Thread(new MessageClient("htf0001")).start();
MessageGUI dialog = new MessageGUI(collaID);
}
#Override
public void run() {
//loop read from server
// The default port.
int portNumber = 50000;
// The default host.
String host = "localhost";//"54.169.62.79";
/*
* Open a socket on a given host and port. Open input and output streams.
*/
try {
clientSocket = new Socket(host, portNumber);
oos = new ObjectOutputStream(clientSocket.getOutputStream());
oos.flush();
ois = new ObjectInputStream(clientSocket.getInputStream());
Staff staff = new Staff();
sendToServer(staff.connectToMessageServer(collaID));
sendToServer(staff.getMessageLog(collaID));
while(!close){
ArrayList<String> input = new ArrayList<String>();
Object o = ois.readObject();
input = (ArrayList<String>) o;
if(input.get(0).compareTo("end")!=0){
for(int i=0;i<input.size();i=i+5){
MessageGUI.addMessage(input.get(i),input.get(i+1), input.get(i+2),
input.get(i+3),input.get(i+4));
}
}
else close = true;
}
oos.close();
ois.close();
clientSocket.close();
}
catch (UnknownHostException uhe) {
JOptionPane.showMessageDialog(null,uhe.getMessage());
}
catch (IOException ioe) {
JOptionPane.showMessageDialog(null,ioe.getMessage());
}
catch (ClassNotFoundException ex) {
JOptionPane.showMessageDialog(null,ex.getMessage());
}
}
this is the GUI code part that add in the component
public static void addMessage(String date, String firstName, String lastName,
String message, String time){
String newUser = firstName + " " + lastName;
if(recentDate.compareTo(date)!=0){
JLabel newDate = new JLabel(date);
newDate.setHorizontalAlignment(SwingConstants.CENTER);
addComponent(newDate,nextLine,0,3,1);
recentDate = date;
nextLine++;
}
if(recentUser.compareTo(newUser)==0 && recentTime.compareTo(time)==0){
recentJTextArea.append("\n\n"+message);
}
else{
if(recentUser.compareTo(newUser)==0) newUser = recentUser;
JTextArea temp = new JTextArea();
temp.setFocusable(false);
temp.setBorder(BorderFactory.createTitledBorder(newUser));
temp.setLineWrap(true);
temp.setWrapStyleWord(true);
temp.setEditable(false);
temp.setText(message);
recentJTextArea = temp;
recentUser = newUser;
JLabel newTime = new JLabel(time);
newTime.setHorizontalAlignment(SwingConstants.RIGHT);
recentTime = time;
addComponent(temp,nextLine,0,2,1);
nextLine = nextLine + 1;
addComponent(newTime,nextLine,1,1,1);
nextLine = nextLine + 1;
}
invokeLater(new Runnable() {
public void run() {
ChatLogJScrollPane.getVerticalScrollBar().setValue(ChatLogJScrollPane.getVerticalScrollBar().getMaximum());
}
});
}
Seems like you are updating (ie adding the chat text) to your text areas on a thread that is not the GUI thread. Instead, you should call
SwingUtilities.invokeLater(new Runnable() {
temp.setText(message);
temp.repaint();
});
I've found the solution. I need to validate the JPanel in my JScrollPanel.
JPanel container
JScrollPanel(container)
SwingUtilities.invokeLater(new Runnable(){
container.validate();
});
I have implemented a chat client. And as of now it only fully works in cmd, because I can't figure out how I'm supposed to implement the send button in the chat client GUI.
So this is my chat client class:
class ChatClientClass {
private String host;
private int port;
private Socket socket;
private PrintWriter out;
private BufferedReader in;
private BufferedReader stdIn;
ChatWindow chatWindow = new ChatWindow();
public ChatClientClass(String host, int port) {
try {
this.socket = new Socket(host, port);
this.out =
new PrintWriter(socket.getOutputStream(), true);
this.in =
new BufferedReader(
new InputStreamReader(socket.getInputStream()));
this.stdIn =
new BufferedReader(
new InputStreamReader(System.in));
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
chatWindow.addText("You: " + userInput);
String serverMessage = in.readLine();
chatWindow.addText(serverMessage);
}
} catch (UnknownHostException e) {
System.err.println("Couldn't not find host " + host);
e.printStackTrace();
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " + host);
e.printStackTrace();
System.exit(1);
}
}
}
As of now, I can only write stuff in the command prompt. But what I've written as well as what the server answers will appear in my GUI. The addText(String text)-method adds the input and the output to my GUI.
But I can't figure out how to implement my send button. An easy way would be if I could just send a reference to the PrintWriter and a reference to the GUI when I call the constructor of my ActionListener class, and just do something like: thePrintWriter.println( [get the value of the text area that belongs to the send button] ) in the public void actionPerformed(ActionEvent e)-method. But since I can't/shouldn't call the constructor of my ActionListener from my chat client class, hence sending those references. That wont be possible, right?
I also thought about making the PrintWriter variable static, and also making the JTextArea containing the message I want to send variable static, and then create static methods to access these two variables. But it just feels like I'm doing something terribly wrong when I'm doing that. And I can't get that to work either.
So how is a send button in a chat client supposed to be implemented?
Thanks in advance!
If you are new in GUI building in java/eclipse.
I suggest you the gui builder:
http://www.eclipse.org/windowbuilder/
Its really easy to use, and you can make simple GUI-for your app.
To your problem you will need a button to your frame, and you need to add an actionlistener than if you fire it you can do what you want.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
public class ButtonAction {
private static void createAndShowGUI() {
JFrame frame1 = new JFrame("JAVA");
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton(" >> JavaProgrammingForums.com <<");
//Add action listener to button
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
//Execute when button is pressed
//Here call your sender fucntion
out.println(userInput);
chatWindow.addText("You: " + userInput);
String serverMessage = your_jtext.getText();
chatWindow.addText(serverMessage);
System.out.println("You clicked the button");
}
});
frame1.getContentPane().add(button);
frame1.pack();
frame1.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
I try to build server that could talk with several clients at the same time.
But there are some problems with the thread, and the code doesn't work well.
Could you tell me what is wrong with the thread part? Thank you very much.
public class ServerMulti extends JFrame implements ActionListener{
private static final int PORT = 60534;
private JTextField textField = new JTextField();
private static JTextArea textArea = new JTextArea();
private JButton button = new JButton();
public static ServerSocket server;
public static Socket socket;
public static BufferedReader in;
public static PrintWriter out;
public ServerMulti(){
/*
* build up GUI
*/
setTitle("Server Multi");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300,400);
setBackground(Color.white);
setLayout(new BorderLayout());
button.setText("Send");
button.setSize(300, 50);
textField.setText("Type here");
textArea.setSize(300, 50);
add(textField, BorderLayout.NORTH);
add(new JScrollPane(textArea), BorderLayout.CENTER);
add(button, BorderLayout.SOUTH);
setLocation(300, 100);
button.addActionListener(this);
setVisible(true);
}
/*
* print out the information that need to sent to clients
*
*/
public void actionPerformed(ActionEvent e) {
textArea.append(textField.getText()+"\n");
out.println(textField.getText());
textField.setText("");
}
public static void main(String[] args) throws IOException{
new ServerMulti();
//build up the socket and server
try{
server = new ServerSocket(PORT);
socket = server.accept();
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream());
textArea.append("Connected. "+" Port: " + PORT + "\n");
while(true){
worker w = new worker();
Thread t = new Thread(w);
t.start();
}
}catch (IOException e) {
System.out.println("Accept failed:" +PORT);
System.exit(-1);
}
}
//to the same server, different sockets are created connecting to different client
public static class worker implements Runnable{
public void run() {
String msg;
/*
* the "in.readLine()" give the data only once
* 1. so we save the information to a String from the socket
* 2. Then sent out a feedback to client, through socket
* 3. print out the String we just collected
*/
while(true){
try {
msg = in.readLine();
out.println("Server received : " + msg);
textArea.append(msg+"\n");
} catch (IOException e) {
System.out.println("Fail");
e.printStackTrace();
}
}
}
}
}
There are a couple of problems with this, but in terms of multithreading the server: ServerSocket.accept() blocks until a new client attempts to connect. In your code this only happens once; so only one client will be accepted. The layout should instead be something like this:
ServerSocket ss = new ServerSocket(PORT);
while (LISTENING) {
Socket sock = ss.accept();
Handler handler = new Handler(sock);
new Thread(handler).start();
//With the LISTENING variable dealt with as well
}
Here the class Handler should be a Runnable class that deals with the socket on another thread. The while loop can then go back and accept a new connection.
I have a Runnable that reads Console output from an externally called exe (see below) and writes it to both a log file and a JTextArea.
But my Runnable doesn't show the Console output in the JTextArea until the exe completely finishes. How do I get it to print Console output as it happens?
Short Concise Code Example below:
//Main
import java.awt.*;
import java.io.IOException;
import javax.swing.*;
public class Example extends JFrame {
private static final long serialVersionUID = 1L;
public static int maxX, maxY;
public static JTextArea ta = new JTextArea(20, 60);//For LOG display window
public static void main(String args[] ) throws IOException
{
new Example();
}
public Example() {
this.setTitle("Example");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//MAIN Panel
final JPanel main = new JPanel();
JButton RunButton = button.run(main);
main.add(RunButton);
Container container = getContentPane();
container.add(main);
this.pack();
this.setVisible(true);
}
}
//Button Action Listener
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
public class button {
public static JButton run( final JPanel parent ) {
JButton RunButton = new JButton();
RunButton.setText("Start!");
RunButton.addActionListener(
new ActionListener()
{
public void actionPerformed( ActionEvent event)
{
try
{
//Set up LOG Display
JDialog dialog = new JDialog((JFrame)null, "Working...");
JPanel temp_panel = new JPanel();
temp_panel.add(new JScrollPane(Example.ta));
dialog.getContentPane().add(temp_panel);
dialog.pack();
dialog.setVisible(true);
//Build the Command
ArrayList<String> command = new ArrayList<String>();
command.add("ping");
command.add("127.0.0.1");
//Start the process
Process p = new ProcessBuilder(command).start();
//Starts LOG display capture in separate thread
SwingUtilities.invokeLater(new execute(p));
//Wait for call to complete
p.waitFor();
}
catch(Exception err)
{
JOptionPane.showMessageDialog( parent, "Error Executing Run!", "Warning", JOptionPane.ERROR_MESSAGE );
}
}//end ActionPerformed
});
return RunButton;
}
}
//Runnable
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class execute implements Runnable {
String line;
Process p;
public execute ( Process process ) {
p = process;
}
public void run() {
try {
//Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));
while ( (line = is.readLine()) != null ) {
Example.ta.append(line + "\n");
}
System.out.flush();
} catch(Exception ex) { ex.printStackTrace(); }
}
}
Maybe it's because you don't respect Swing's threading policy. All accesses to swing components must be done in the event dispatch thread. Your runnable should thus use SwingUtilities.invokeLater to update the text area in the EDT, rather than in your separate thread.
EDIT : as alf mentions in his comment: JTextArea.append is thread-safe, so it's not absolutely needed here. I would still do it, though, because if the append to a text area was replaced or complemented by any other Swing interaction, it wouldn't be thread-safe anymore.
It could also be that the external process doesn't send any newline character, which makes readLine block until one is found or the end of communication is reached.
Just to help the miner - below's a complete minimalistic (left out everything not absolutely necessary) example that indeed works in my context: each line appears in the textArea as read. It's basically using the SwingWorker as suggested by Justin and re-arranged thingies a bit for clarity.
public class ProcessExample {
public static class ProcessWorker extends SwingWorker<Void, String> {
private JTextArea ta;
private List<String> process;
public ProcessWorker(List<String> command, JTextArea ta) {
this.process = command;
this.ta = ta;
}
#Override
protected Void doInBackground() throws Exception {
Process p = new ProcessBuilder(process).start();
// Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader(new InputStreamReader(
p.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
publish(line);
}
is.close();
return null;
}
#Override
protected void process(List<String> chunks) {
for (String string : chunks) {
ta.append(string + "\n");
}
}
}
private void startProcess(JTextArea ta) {
ArrayList<String> command = new ArrayList<String>();
command.add("ping");
command.add("127.0.0.1");
new ProcessWorker(command, ta).execute();
}
private JComponent getContent() {
JPanel main = new JPanel(new BorderLayout());
final JTextArea ta = new JTextArea(20, 60);
main.add(new JScrollPane(ta));
Action action = new AbstractAction("Start!") {
#Override
public void actionPerformed(ActionEvent e) {
startProcess(ta);
}
};
main.add(new JButton(action), BorderLayout.SOUTH);
return main;
}
public static void main(String args[]) throws IOException {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ProcessExample().getContent());
frame.pack();
frame.setVisible(true);
}
});
}
}
You could try the same logic with a SwingWorker instead. You can extend this class, instead of implementing runnable. It could take your Text area as a paramater, and you can publish the data, without having to deal with the SwingUtils.invokeLater, which is guiltily easier...
Try:
public class execute extends javax.swing.SwingWorker {
String line;
Process p;
JTextArea jta;
File f = new File( properties.prop.getProperty( "LOG_FILE_DIR" ) + "\\PartGen.log");
public execute ( Process process , JTextArea jta ) {
p = process;
this.jta = jta;
}
//implements a method in the swingworker
public void doInBackground() throws Exception {
//Read Process Stream Output and write to LOG file
BufferedReader is = new BufferedReader( new InputStreamReader(p.getInputStream()));
while ( (line = is.readLine()) != null ) {
osfile.writeline(line, f);
publish(new String(line + "\n"));
}
System.out.flush();
return null;
}
//This will happen on the UI Thread.
public void process(List lines){
for(Object o : lines){
jta.append((String)o);
}
}
public void done(){
try{
get();
//You will get here if everything was OK. So show a popup or something to signal done.
}catch(Exception ex){
//this is where your IO Exception will surface, should you have one.
}
}
}
Also, in your calling code, which I assume is in your ui somewhere:
Process p = new ProcessBuilder(command).start();
execute ex = new execute( p , yourTextArea);
ex.execute();
I didnt attempt to compile this, so you may have to check against the API, but hopefully it will give you a gist of what to do.
The problem was not that the thread wasn't capturing the data, it was the JTextArea just not refreshing. repaint(), revalidate(), and updateUI() did not refresh the JTextArea, but the following did:
Example.ta.update(Example.ta.getGraphics());
The problem in this case is with the waitFor:
p.waitFor();
This causes the Button Action Listener to wait on that point until the process is completed.
hello friends i have created a UDP chatting program through which the clients can communicate over LAN.
I have created a genaralized program i.e. I run the same code with different port nos. and IP address when on LAN
My problem is that This code below works fine on localhost but when i try to connect two machines this code doesnt work..there is no error as of such but still the two clients cant communicate
I have also switched off the firewalls.But i am unable to find out why i cant communicate between two machines
The Code is as follows ::
import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
class ChatApplDG extends Frame implements ActionListener
{
TextField tf = new TextField(50);
Button btn = new Button("Send");
Button exit = new Button("Exit");
TextArea ta = new TextArea(50,10);
int fromPort, toPort;
String hostName;
DatagramSocket dgSock;
public static void main(String args[]) throws Exception
{
ChatApplDG ca = new ChatApplDG();
ca.startClient(args[0],Integer.parseInt(args[1]),Integer.parseInt(args[2]));
}
ChatApplDG()
{
Panel p = new Panel();
add(p,BorderLayout.NORTH);
p.add(tf);
p.add(btn);
p.add(exit);
add(ta,BorderLayout.CENTER);
btn.addActionListener(this);
exit.addActionListener(this);
setSize(500,300);
show();
ta.setEditable(false);
}
void startClient(String hName,int fPort,int tPort)
{
try
{
hostName = hName;
fromPort=fPort;
toPort=tPort;
dgSock = new DatagramSocket(fromPort);
ta.append("Ready To Send ...\n");
RunningThreadDG rt = new RunningThreadDG(dgSock,ta);
Thread thread = new Thread(rt);
thread.start();
}
catch(Exception e)
{
}
}
public void actionPerformed(ActionEvent ae)
{
if(ae.getSource()==btn)
{
try
{
byte buff[] = new byte[500];
InetAddress remoteHost = InetAddress.getByName(hostName);
buff=tf.getText().getBytes();
dgSock.send(new DatagramPacket(buff,buff.length,remoteHost.getLocalHost(),toPort));
ta.append("Send : " + tf.getText() + "\n");
tf.setText("");
}
catch(Exception e)
{
}
}
else
{
System.exit(0);
}
}
}
class RunningThreadDG extends Frame implements Runnable
{
DatagramSocket dgSock;
TextArea ta;
String str;
RunningThreadDG(DatagramSocket dgs,TextArea t)
{
dgSock=dgs;
ta=t;
}
public void run()
{
byte[] buff = new byte[500];
while(true)
{
try
{
DatagramPacket dgPack = new DatagramPacket(buff,buff.length);
dgSock.receive(dgPack);
ta.append("Received : " + new String(dgPack.getData(),0,dgPack.getLength()) + "\n");
}
catch(Exception e)
{
}
}
}
}
Here's a problem:
dgSock.send(new DatagramPacket(buff,buff.length,remoteHost.getLocalHost(),toPort));
remoteHost.getLocalHost() returns an InetAddress for your local host. Try just passing remoteHost instead of remoteHost.getLocalHost():
dgSock.send(new DatagramPacket(buff,buff.length,remoteHost,toPort));
In addtion to echo's answer I would like to add that you should at least add e.printStackTrace(); to your catch blocks.
Also check whether both machines can resolve each others hostnames by calling nslookup hostname. Or just ping hostname from each machine.