How to end a thread handling socket connection?

805 views Asked by At

I have a thread handling a socket connection:

BufferedReader socketInput = new BufferedReader(new InputStreamReader(mySocket.getInputStream()));
while (true)
{
    String line = socketInput.readLine();
    // do stuff
}

As I've read in a few answers on this site, the recommended solution is to use a flag which one thread sets and my (socket handling) thread checks and terminates itself when that flag changes state. Something like:

while (!done)
{
    String line = socketInput.readLine();
    // do stuff
}

But this can get stuck when readLine() is still waiting for input. I guess I could set a timeout:

mySocket.setSoTimeout(100);
while (!done)
{
    String line = socketInput.readLine();
    // do stuff
}

Which would probably work but I would still get a 100 ms delay before my thread "realizes" the flag's state changed.

Is there a way for the thread to "realize" right away that it should end? If not, is my solution (with timeout and flag done) correct?

Edit: I've clarified that the socketInput is of type BufferedReader (alternatively I'm considering Scanner).

4

There are 4 answers

8
Matthias247 On

The most common way to handle this is to close the socket from the other Thread. This will lead the reading side to unblock and exit with the (expected) error that the socket was closed. Depending on the socket API that you have available it might also be possible to shutdown only the reading side. From a short look at the JDK shutdownInput() might work.

If you however want to continue to read from the socket later on these obvisouly won't work. Your solution should work there, but is obvisouly worse for performance and reactivity since you basically poll the socket all 100ms.

0
Matthieu On
  1. Create a Selector
  2. Configure your socket.getChannel() to non-blocking and register it to the Selector with SelectionKey.OP_READ
  3. Call your Selector select() method that will return when there are some data to read so you can call readLine() (i.e. select() returns > 0)

Whenever you want to end your socket processing, set your done flag and call your Selector wakeup() method. That will make the select() return immediately (potentially 0, or 1 if there was activity). You can then check your done flag and end your thread gracefully.

Here is a quick implementation. Notice I pass the BufferedReader as an argument as if you're opening it in the thread you should also close it there, which would close the socket too, so it has to be done outside. There are two methods to signal the thread to gracefully stop processing input and one to send data:

public class SocketHandler extends Thread {

    private Socket sok;
    private BufferedReader socketInput;

    private Selector sel;
    private SocketChannel chan;
    private boolean done;

    public SocketHandler(Socket sok, BufferedReader socketInput) throws IOException {
        this.sok = sok;
        chan = sok.getChannel();
        chan.configureBlocking(false);
        sel = Selector.open();
        chan.register(sel, SelectionKey.OP_READ);
        this.socketInput = socketInput;
        done = false;
    }

    @Override
    public void run() {
        while (!done) {
            try {
                if (sel.select() == 0)
                    continue;
            } catch (IOException e) {
                e.printStackTrace();
            }

            // Only one channel is registered on only one operation so we know exactly what happened.
            sel.selectedKeys().clear();
            doRead();
            // Otherwise: loop through sel.selectedKeys(), check for readability and clear the set
        }
        try {
            sel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doRead() {
        try {
            String line = socketInput.readLine();
            // TODO: process 'line'
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void signalStop() {
        done = true;
        if (sel != null)
            sel.wakeup(); // Get out of sel.select()
    }

    public void doWrite(byte[] buffer) throws IOException { // Or "String message"
        sok.getOutputStream().write(buffer); // Or anything else
    }

}
1
thepaulo On

The best way to know when finish a socket connection is to try to read something. If read method return -1 you can end threadling socket connection

byte[] data = new byte[2048];
while (!done) {
    int count = input.read(data);
    if (count <= 0) {
        if (count < 0)
            done = true;
        continue;
    }
    String request = new String(data, 0, count);
    //do stuff
}

We try to read something in input if count == -1, the socket client is disconnected now we can end the loop, by changing the value of done.

10
Pascal Heraud On

The solution is correct, it will exit when done is set to true. And yes, the readLine will always wait for 100ms, if you don't want to wait you may interrupt the thread by calling thread.interrupt() it but it's not very clean way.