I am currently making a boardgame that allows two player through a socket. I am also trying to add in a game chat, but reguardless I need a way for the Frame class to act as normal (Jbuttons, enter text into a jscroll area, etc) but also send & look for incoming objects. I figured implementing runnable would be the best option.
here is my run method:
public void run() {
while(running){
Object obj = null;
try {
obj = connection.receiveObjects();
} catch (ClassNotFoundException) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
running = false;
if(obj != null){
if(obj instanceof String){
showMessage((String)obj);
}
}
}
}
My connection class is just a server or a client, and uses these methods:
public void sendObjects(Object obj) throws IOException{
output.writeObject(obj);
output.flush();
}
public Object receiveObjects() throws IOException, ClassNotFoundException{
return input.readObject();
}
At the Frame class's constructor I call
Thread thread = new Thread(this);
thread.start();
In hopes that the run method will not interfere with the actionPerformed method. But to my dismay, when I click a jbutton (all it does is call the send() method), the program freezes, and nothing is sent over.
Here is the other necessary code:
private JTextArea textBox;
private JScrollPane pane;
private JTextArea userText;
private JScrollPane userPane;
private void send(){
String message = "YOU- " + userText.getText();
userText.setText("");
String totalMessage = textBox.getText();
textBox.setText(totalMessage + "/n" + message);
try {
connection.sendObjects(message);
} catch (IOException e) {
e.printStackTrace();
}
}
public void showMessage(String s){
String totalMessage = "Opponent- " + textBox.getText();
textBox.setText(totalMessage + "/n" + s);
}
My hope is that once I get the chat working, I can also add in sending over the pieces and calling other methods to do everything else I need (via instanceof). I could probably make a separate class that implements runnable to just deal with data transfer, but I was hoping I could do it all in one class, and do not see why I cannot. I did spend at least an hour and a half looking up multithreading, but I could not figure my problem out. I apologize If I am looking over anything apparent, but mutithreading and sockets are far beyond my comfort zone and AP Comp sci class education.
Besides the UI thread, you need:
one thread to constantly listen for incoming data
one thread to send data
You have #1 but not #2, so modify send() method as follows:
private void send() {
// Do the UI tasks on the UI thread
final String message = "YOU- " + userText.getText();
userText.setText("");
String totalMessage = textBox.getText();
textBox.setText(totalMessage + "\n" + message);
// Start new thread to do the networking (send data)
new Thread(new Runnable() {
#Override
public void run() {
try {
connection.sendObjects(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
I have a threading problem of some kind outputting to a JTextArea. I have a BufferedWriter set up to write to this text area, but for some reason it is not writing to this area until the current function is done. For example, I have this code that checks a list of URLs to see if they are good or bad:
for( final String s : urls ){
String sContent = IO.getURLContent( s );
if( sContent == null ){
writeLine( "Error " + s );
} else {
writeLine( "Worked " + s );
}
}
void writeLine( String sLineText ) throws java.io.IOException {
mbwriter.write(sLineText);
mbwriter.newLine();
mbwriter.flush();
}
The output is only occurring AFTER the loop is done, even though the writer is being flushed after every URL is checked. I tried putting the IO operation in a separate thread like this:
for( final String s : urls ){
SwingUtilities.invokeLater( new Runnable() {
public void run(){
String sContent = IO.getURLContent( s );
if( sContent == null ){
writeLine( "Error " + s );
} else {
writeLine( "Worked " + s );
}
}
}
} );
}
But it makes no difference, the output is still blocked until the entire loop is complete.
Note that the ENTIRE Swing interface is hung during this operation. You can't click on anything, even the window closer. The interface is completely frozen.
This would be expected.
Swing is a single threaded environment, that is, if you block the Event Dispatching Thread with long running process or block method calls, it will be unable to process new events posted to the Event Queue, effectively making your UI "hang"
It sounds like your first example is running within the context of the EDT and your second example is still running within the context of the EDT, but is schedule an update at some time in the future, after the EDT is able to, once again, process the Event Queue...
invokeLater is placing an event on the Event Queue for the EDT to process "later"
Consider using a SwingWorker instead, it provides the ability to run code in the background but provides methods that allow you to synchronise the updates to the EDT via it's publish and process methods
See Concurrency in Swing and
Worker Threads and SwingWorker for more details
As an example...
public class URLWorker extends SwingWorker<List<String>, String> {
private List<String> urls;
private BufferedWriter bw;
public URLWorker(List<String> urls, BufferedWriter bw) {
this.urls = urls;
this.bw = bw;
}
#Override
protected void process(List<String> chunks) {
for (String result : chunks) {
try {
bw.write(result);
bw.newLine();
} catch (IOException exp) {
firePropertyChange("error", null, ex);
}
}
try {
bw.flush();
} catch (IOException ex) {
firePropertyChange("error", null, ex);
}
}
#Override
protected List<String> doInBackground() throws Exception {
List<String> results = new ArrayList<String>(urls.size());
for (final String s : urls) {
String sContent = IO.getURLContent(s);
StringBuilder result = new StringBuilder(s);
if (sContent == null) {
result.insert(0, "Error ");
} else {
result.insert(0, "Worked ");
}
publish(result.toString());
results.add(result.toString());
}
return results;
}
}
I am creating an IRC bot in Java. I made a class called "loginHandler" which asks the user to input the login info, such as nick/pass/server/etc... When the user is done, the "Connect" button is clicked, which initializes the IRC handler class, bot. See the code:
package irc_bot;
import java.awt.BorderLayout;
//more imports are here but they are irrelevant right now
public class loginHandler extends JFrame {
private static final long serialVersionUID = 6742568354533287421L;
private bot irc_bot;
private String username;
private String password;
private int[] ports={80,6667};
//.....
//gui vars
private final JTextField usernameInput;
private final JTextField passwordInput;
//......
public loginHandler(){
super("Login data");
Panel = new JPanel(new BorderLayout());
Panel.setLayout(new GridLayout(13, 1));
JLabel label = new JLabel("Hover over labels for information!!");
Panel.add(label);
label = new JLabel("Username: ");
label.setToolTipText("Type in your username!");
Panel.add(label);
usernameInput=new JTextField("");
usernameInput.setEditable(true);
Panel.add(usernameInput);
label = new JLabel("Password: ");
label.setToolTipText("Type in your password! Starts with 'oauth:'");
Panel.add(label);
passwordInput=new JPasswordField("");
passwordInput.setEditable(true);
Panel.add(passwordInput);
//.......
//the other textfields are here but they are irrelevant right now
//The important part:
JButton okButton=new JButton("Connect");
okButton.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
//set data method
setData();
dispose();
//bot object:
try {
irc_bot=new bot(username, password, master_channel, master_admin);
} catch (Exception e) {
e.printStackTrace();
}
//initiate the bot:
try {
irc_bot.initiate(server, ports);
} catch (Exception e) {
e.printStackTrace();
}
}
}
);
add(okButton, BorderLayout.SOUTH);
add(new JScrollPane(Panel), BorderLayout.NORTH);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300,400);
setVisible(true);
}
private void setData(){
username=usernameInput.getText();
password=passwordInput.getText();
server=serverInput.getText();
master_channel=master_channelInput.getText();
master_admin=master_adminInput.getText();
port=portsInput.getText();
//set up the ports: TO-DO
}
}
My problem is that the bot.initiate() method blocks the GUI of the bot object. When the bot.initiate() is not called, the GUI works as intended. When the bot.initiate() stops, the GUI will be functional again. The problem is that the initiate() method contains an infinite loop which reads the lines from the irc server (this is in irc_bot.mainMethod()).
The GUI window shows up, but it is blank, and it does not respond to me trying to close it or anything else.
The program is actually not frozen, I can still communicate with the bot via irc.
The weird thing is that if I initiate the bot object in main() for example, it works as intended, the initiate() method does not block the gui.
Take a look at the bot class (I copied only the relevant parts):
package irc_bot;
//import stuff
public class bot {
//gui vars
private JFrame mainWindow;
//.....
//irc variables
private String server;
//.....
//lists
private List<String> botadminlist;
//.......
//for communicating with IRC
private Socket socket;
private BufferedWriter writer;//write to IRC
//.....
pritate bot_msg_handler handler;
private String line;//the received line from the IRC server is stored in this
//-----methods-----
//constructor: does the basic setup
public bot(String nick, String password, String master_channel, String master_admin) throws Exception {
//gui stuff
mainWindow=new JFrame("irc_bot");
//setup the menu
menuBar = new JMenuBar();
//first menu
menu = new JMenu("Commands");
menu.setMnemonic(KeyEvent.VK_C);
menuBar.add(menu);
//ide jön a menü kifejtése
menuItem = new JMenuItem("Show/Edit commands",
KeyEvent.VK_S);
//event handling
menuItem.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
showCommands();
}
}
);
menu.add(menuItem);
menuItem = new JMenuItem("Add command",
KeyEvent.VK_A);
//event handling
menuItem.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent event){
addCommand();
}
}
);
menu.add(menuItem);
//more menus.......
//more GUI elements
//.......
//setup the window
mainWindow.add(bottomPanel,BorderLayout.SOUTH);
mainWindow.add(menuBar, BorderLayout.NORTH);
mainWindow.add(new JScrollPane(textBox), BorderLayout.CENTER);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWindow.setSize(800,600);
mainWindow.setVisible(true);
sendBotMsg=false;
//setup lists
modlist=new ArrayList<mod_element>();
//.....
//user settings
this.nick=nick;
this.password=password;
this.master_channel=master_channel;
this.master_admin=master_admin;
//create the message handler object
handler = new bot_msg_handler(this.nick, this.master_channel, this.master_admin, modlist,
botadminlist, stafflist, adminlist, active_channels, cooldown, commands);
handler.setTextBox(textBox);
textBox.append("Constructor setup complete\n");
}//constructor
//IRC SPECIFIC STUFF
public void initiate(String server, int... ports) throws Exception{
this.server=server;
if(connect(server, ports)==-1){
JOptionPane.showMessageDialog(null, "Bot couldn't connect to the server!");
mainWindow.dispose();
return;
}
if(logon()==-1){
JOptionPane.showMessageDialog(null, "Bot couldn't log in!");
mainWindow.dispose();
return;
}
join(master_channel);
mainMethod();
}
private int connect(String server, int... ports) throws Exception {
// Connect directly to the IRC server.
//this.server=server;
boolean connected=false;
for(int port:ports){
textBox.append("Trying to connect to "+server+" on port "+port+"...\n");
try{
socket = new Socket();
socket.setSoTimeout(1000);//if nothing happens in 1000 milliseconds, it is gonna advance in the code. IMPORTANT!
socket.connect(new InetSocketAddress(server, port), 2000);//2000 is the timeout value in milliseconds for the connection
textBox.append("Connected to "+server+":"+port+"\n");
connected=true;
this.port=port;
break;
}
catch(SocketTimeoutException e1){
textBox.append("Connection timed out\n");
}
}
if(connected){
writer = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream( ), "UTF-8"));//utf-8 (international characters)
reader = new BufferedReader(
new InputStreamReader(socket.getInputStream( ), "UTF-8"));
handler.setWriter(writer);
return 1;//connection successful
}else{
textBox.append("Connection timed out, cannot connect to the IRC server\nApp will shut down now.\n");
return -1;//this means that the connection failed
}
}//connect
private int logon() throws Exception {//logs the bot in the irc
writer.write("PASS " + password + "\r\n");//twitch specific stuff
writer.write("NICK " + nick + "\r\n");
writer.flush( );
// Read lines from the server until it tells us we have connected.
String line = null;
while ((line = reader.readLine()) != null) {
rawBox.append(line+"\n");
if (line.contains("004")) {
// We are now logged in.
textBox.append("The bot is successfully logged in\n------------------\n");
return 1;
}
else if (line.contains("Login unsuccessful")) {
textBox.append("Login was unsuccessful.\n");
return -1;
}
}
return -1;
}//logon
private void join(String channel) throws Exception {
// Join the channel. Only for initial use.
writer.write("JOIN " + channel + "\r\n");
writer.flush( );
writer.write("TWITCHCLIENT 3"+"\r\n");
writer.flush();
}//join
private void mainMethod() throws Exception{
// Keep reading lines from the server.
//-------MAIN PROCESS------
msgInput.setEditable(true);//the textbox is ready to be used
while (true){
try{
line = reader.readLine( );//waits for a line for 1000 millisecs
handler.setLine(line);
}catch(SocketTimeoutException e7){
//if there is no incoming line in a second, it's gonna create an exception
//we want nothing to do with the exception though
}
if(line!=null){
handler.set_msg_type(0);//default for each line
if (line.startsWith("PING ")) {
// We must respond to PINGs to avoid being disconnected.
handler.sendPONG();
}
//Print the raw line received by the bot to the rawBox
rawBox.append(line+"\n");
//analyze the line and gather information
handler.msgAnalyzer();
//message handling:
handler.msgHandler();
//update channellist and other lists
updateLists();
}//if
//other tasks
handler.otherTasks();
line=null;
//send the message
if(sendBotMsg){
handler.sendPRIVMSG(channelToSend, msgToSend);
sendBotMsg=false;
}
}//while
}//mainMethod
//Methods called from the gui are here, but they are irrelevant now
I have tried adding SwingWorker so the initiate stuff runs in the background, but it still freezes the gui.
The program works as intended when I ...
Do not call the initiate method. I still create the object in the actionPerformed method, and I get a functioning GUI.
Do not call the initiate function from the actionPerformed.
For example, when I do this, the bot works as intended:
public static void main(String[] args) throws Exception {
String server="irc.twitch.tv";
int[] ports={80,6667};
String nick ="test";
String password = "test";
String master_channel = "#master";
String master_admin="master";
//bot object:
bot irc_bot=new bot(nick, password, master_channel, master_admin);//this is gonna be our irc_bot object
//initiate the bot:
irc_bot.initiate(server, ports);
}
I suspect the thread in which the initiate() runs is blocking the GUI thread somehow. The thing that I do not understand is why it only happens when I call said method from the action listener/window listener/any listener. Any ideas as to how I can fix this?
The button click anonymous class's actionPerformed() method is executed on the Swing thread, so while the code in that block is executed, the GUI cannot do anything else. You need to execute the initiate() method in some other thread.
To prove to yourself that this is the case, use this (terrible) code:
new Thread(){
public void run() {
//initiate the bot:
try {
irc_bot.initiate(server, ports);
} catch (Exception e) {
e.printStackTrace();
}
}}.start();
That should achieve what you are looking for, albeit with terrible code. Then you need to work out how you are going to create and manage that thread. You'll need a way to signal, from your GUI, to the background thread that you want it to stop, probably by interrupting it.
The reason that you do not get this issue when executing the code from the main() method is that you are getting a new Thread for free. When you start your app with main(), you call the constructor on bot and this spawns the UI. Your main method, in its main thread then start executing the bot initiate() method and gets into that loop, whilst the Swing thread is responsible for running the UI.
For button clicks, actionPerformed, is called on the event dispatching thread, and one should return from actionPerformed as fast as possible. Use the invokeLater to do long work a bit later. Otherwise the event queue is blocked (single thread), and the GUI is not responsive.
public void actionPerformed(ActionEvent event){
SwingUtilites.invokeLater(new Runnable() {
#Override
public void run() {
... the work
}
});
}
EventQueue.invokeLater(() -> {
... the work
});
The second alternative uses the alternative class for invokeLater, and shortens the code using java 8's lambdas.
I have tried adding SwingWorker so the initiate stuff runs in the
background, but it still freezes the gui.
You must call execute() on SwingWorker, not run() method (common mistake).
Like this:
new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
irc_bot.initiate(server, ports);
return null;
}
}.execute();
I'm trying to create a simple client/server app in java, it has a main that starts a thread - the listener thread and a runnable - the sender thread.
They communicate with a working program.
The socket is created on the Listener thread, as are the input and output static variables.
The problem is :
I can use the output, but only if I call it from the listener thread where it is defined.
(output.writeBytes("0000");)
When I try calling it from the Sender runnable, I get a null exception!!
(InfoListener.output.writeBytes("0000");)
Here is my (not so smart) code, without all the exception handling :
* InfoListener.java file *
public class InfoListener extends Thread {
public int port = 5000;
public Socket socket = null;
static BufferedReader input;
static DataOutputStream output;
static boolean can_start_sender = false;
static boolean active = true;
static String answer="";
public void run()
{
// init socket
socket = new Socket("127.0.0.1", port);
output = new DataOutputStream(socket.getOutputStream());
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
can_start_sender = true;
while (active) // while main app is active
{
// Read new data!!
answer = input.readLine();
if (answer!=null)
{
System.out.println("Info : Listener received : " + answer);
}
}
}
}
* InfoSender.java file *
public class InfoSender implements Runnable {
static InfoListener infoListener;
static InfoSender infoSender;
static String string_to_send = "0000";
public static void main(String[] args)
{
// start listener
infoListener = new InfoListener();
infoListener.start();
// Start Sender
infoSender = new InfoSender();
infoSender.run();
while (infoListener.isAlive())
Thread.sleep(100);
}
public void run()
{
//attempt to connect to the server and send string
// Wait for socket
while (InfoListener.can_start_sender = false)
Thread.sleep(100);
// write -------- HERE IS THE NULLPOINTEREXCEPTION ------------
InfoListener.output.writeBytes(string_to_send);
System.out.println("Info : info sent :"+ string_to_send);
// wait a bit for listener to get response back, then close
Thread.sleep(10000);
InfoListener.active = false;
}
}
Please help :|
In
while (InfoListener.can_start_sender = false)
you are assigning false to can_start_sender. The while will therefore always resolve to false.
It's possible that code following the while
// write -------- HERE IS THE NULLPOINTEREXCEPTION ------------
InfoListener.output.writeBytes(string_to_send);
will get executed before the other Thread has time to initialize the static output field thus causing a NullPointerException.
Use
while (!InfoListener.can_start_sender)
or better yet use a CountDownLatch or similar java.util.concurrent lock object. You should also make can_start_sender volatile.
I'm creating a client window that retrieves from a javaSpace, this is the code I'm using.
/**
* Create the frame.
*/
public Client()
{
space = SpaceUtils.getSpace();
if (space == null)
{
System.err.println("Failed to find the javaspace");
System.exit(1);
}
initFrame();
setVisible(true);
processPrintJobs();
}
The window is generated inside of initFrame(); and then processPrintJobs checks to see if there are any new messages. If I comment out the processPrintJobs() method call then the window draws correctly but if the method call is there, the window just shows a blank square.
Its like the window is not being created correctly due to the process being checked lots of times, which makes no sense as the window is created before the while loop is run.
public void processPrintJobs()
{
while (true)
{
try
{
Message template = new Message();
if (channel == null)
{
System.out.println("No channel given");
} else
{
template.Channel = channel;
// System.out.println(channel);
template.position = new Integer(getNumber() + 1);
Message msg = (Message) space.read(template, null,
Long.MAX_VALUE);
messageList.append(msg.execute());
}
} catch (Exception e)
{
e.printStackTrace();
}
}
}
This infinite while loop will block the EDT.
while (true)
Simply calling
setVisible(true);
does not guarantee that the JFrame will be painted immediately. Any long-lived processes should be handled by a SwingWorker.
Use
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
processPrintJobs();
}
}
But in general, the architecture of your application is missing some aspects.
Like a thread or whatever.
More:
You could for instance use a swing Timer, for one single job every tick.
import javax.swing.Timer;
To be called at the end of the Client constructor.
Timer printJobsTimer = new Timer(100, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Process a print job:
if (channel != null) {
Message template = new Message();
template.Channel = channel;
template.position = new Integer(getNumber() + 1);
Message msg = (Message) space.read(template, null,
Long.MAX_VALUE);
messageList.append(msg.execute());
}
}
});
printJobsTimer.setInitialDelay(100);
printJobsTimer.start();
For the rest, consisting naming would have been fine: just class names starting with a capital and other names with a small letter.