Squagem
Squagem

Reputation: 734

How Do You Stop A Thread Blocking for Network I/O?

I am currently trying to write a very simple chat application to introduce myself to java socket programming and multithreading. It consists of 2 modules, a psuedo-server and a psuedo-client, however my design has lead me to believe that I'm trying to implement an impossible concept.

The Server

The server waits on localhost port 4000 for a connection, and when it receives one, it starts 2 threads, a listener thread and a speaker thread. The speaker thread constantly waits for user input to the console, and sends it to the client when it receives said input. The listener thread blocks to the ObjectInputStream of the socket for any messages sent by the client, and then prints the message to the console.

The Client

The client connects the user to the server on port 4000, and then starts 2 threads, a listener and s speaker. These threads have the same functionality as the server's threads, but, for obvious reasons, handle input/output in the opposite way.

The First Problem

The problem I am running into is that in order to end the chat, a user must type "Bye". Now, since my threads have been looped to block for input:

    while(connected()){

        //block for input
        //do something with this input
        //determine if the connection still exists (was the message "Bye"?)
    }

Then it becomes a really interesting scenario when trying to exit the application. If the client types "Bye", then it returns the sending thread and the thread that listened for the "Bye" on the server also returns. This leaves us with the problem that the client-side listener and the server-side speaker do not know that "Bye" has been typed, and thus continue execution.

I resolved this issue by creating a class Synchronizer that holds a boolean variable that both threads access in a synchronized manner:

public class Synchronizer {

    boolean chatting;

    public Synchronizer(){

        chatting = true;
        onChatStatusChanged();
    }

    synchronized void stopChatting(){

        chatting = false;
        onChatStatusChanged();
    }

    synchronized boolean chatting(){

        return chatting;
    }

    public void onChatStatusChanged(){

     System.out.println("Chat status changed!: " + chatting);
    }
}

I then passed the same instance of this class into the thread as it was created. There was still one issue though.

The Second Problem

This is where I deduced that what I am trying to do is impossible using the methods I am currently employing. Given that one user has to type "Bye" to exit the chat, the other 2 threads that aren't being utilized still go on to pass the check for a connection and begin blocking for I/O. While they are blocking, the original 2 threads realize that the connection has been terminated, but even though they change the boolean value, the other 2 threads have already passed the check, and are already blocking for I/O.

This means that even though you will terminate the thread on the next iteration of the loop, you will still be trying to receive input from the other threads that have been properly terminated. This lead me to my final conclusion and question.

My Question

Is it possible to asynchronously receive and send data in the manner which I am trying to do? (2 threads per client/server that both block for I/O) Or must I send a heartbeat every few milliseconds back and forth between the server and client that requests for any new data and use this heartbeat to determine a disconnect?

The problem seems to reside in the fact that my threads are blocking for I/O before they realize that the partner thread has disconnected. This leads to the main issue, how would you then asynchronously stop a thread blocking for I/O?

I feel as though this is something that should be able to be done as the behavior is seen throughout social media.

Any clarification or advice would be greatly appreciated!

Upvotes: 1

Views: 1974

Answers (2)

Squagem
Squagem

Reputation: 734

Just to clarify for anyone who might stumble upon this post in the future, I ended up solving this problem by tweaking the syntax of my threads a bit. First of all, I had to remove my old threads, and replace them with AsyncSender and AsyncReader, respectively. These threads constantly send and receive regardless of user input. When there is no user input, it simply sends/receives a blank string and only prints it to the console if it is anything but a blank string.

The Workaround

try{        
    if((obj = in.readObject()) != null){

    if(obj instanceof String)
        output = (String) obj;

    if(output.equalsIgnoreCase("Bye"))
    s.stop();
    }
}
catch(ClassNotFoundException e){

    e.printStackTrace();
}
catch(IOException e){

    e.printStackTrace();
}

In this iteration of the receiver thread, it does not block for input, but rather tests if the object read was null (no object was in the stream). The same is done in the sender thread.

This successfully bypasses the problem of having to stop a thread that is blocking for I/O.

Note that there are still other ways to work around this issue, such as using the InterruptableChannel.

Upvotes: 1

Narf the Mouse
Narf the Mouse

Reputation: 1568

I don't know Java, but if it has threads, the ability to invoke functions on threads, and the ability to kill threads, then even if it doesn't have tasks, you can add tasks, which is all you need to start building your own ASync interface.

For that matter, if you can kill threads, then the exiting threads could just kill the other threads.

Also, a "Bye" (or some other code) should be sent in any case where the window is closing and the connection is open - If Java has Events, and the window you're using has a Close event, then that's the place to put it.

Alternately, you could test for a valid/open window, and send the "Bye" if the window is invalid/closed. Think of that like a poor mans' event handler.

Also, make sure you know how to (and have permission to) manually add exceptions to your networks' firewall(s).

Also, always test it over a live network. Just because it works in a loopback, doesn't mean it'll work over the network. Although you probably already know that.

Upvotes: 1

Related Questions