-1

I am doing socket programming in Java and I am confused by setSoTimeout() and its exceptions.

I have a simple Callable<String> on the server-side for accepting connections:

private Callable<String> waitForConnectionCallable = new Callable<String>() {

    @Override
    public String call() throws IOException {
        if (socket == null) {
            socket = new ServerSocket(PORT);
        }

        socket.setSoTimeout(CONNECTION_TIMEOUT);
        Socket inSocket = socket.accept();
        
        return "CONNECTED";
    }
};

And I call and handle it in this way:

    try {
        Future<String> connectionFuture = executorService.submit(waitForConnectionCallable);
        String connectionResult = connectionFuture.get();
    } catch (ExecutionException | InterruptedException e) {
        Logs.error(TAG, "No connection received");
        e.printStackTrace();
    }

Now, the java doc for setSoTimeout() mentions raising a SocketTimeoutException, though strangely it is not included in the Throws list:

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a positive timeout value, a call to accept() for this ServerSocket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the ServerSocket is still valid. A timeout of zero is interpreted as an infinite timeout. The option must be enabled prior to entering the blocking operation to have effect.

Params: timeout – the specified timeout, in milliseconds

Throws: SocketException – if there is an error in the underlying protocol, such as a TCP error IllegalArgumentException – if timeout is negative

When I run the program and the connection times out, I get the SocketTimeoutException in the catch block, but in this way:

java.util.concurrent.ExecutionException: java.net.SocketTimeoutException: Accept timed out

My question is, how can I catch that specific exception? I can't add it to my caller's catch statement, because I get the compile error of it not being thrown in the method. Also, I can't add to the call() method (in the Callable), since IOException, a more general exception is already there.

Additionally: why SocketTimeoutException is not part of the setSoTimeout() signature? Considering it is not a subclass of SocketException.

Mahm00d
  • 3,881
  • 8
  • 44
  • 83
  • You should move your `socket.setSoTimeout(CONNECTION_TIMEOUT)` inside the `if (socket == null)` block: calling it once is enough, not each time. – Matthieu Oct 21 '21 at 06:24
  • 1
    You have a misconception. `Socket.setSoTImeout()` doesn't throw the exception. `ServerSocket.accept()` and the various `read()` methods of the socket input stream or whatever you wrap around it will throw `SocketTimeoutException` if no connection or data respectively arrives within the timeout period. – user207421 Oct 21 '21 at 07:14

2 Answers2

2

You are mixing a few things: the SocketTimeoutException is thrown by socket.accept(). socket.setSoTimeout() can throw an IOException (though it's very seldom).

When the SocketTimeoutException is thrown inside your call() method (by socket.accept()), it will be wrapped inside an ExecutionException, which is a wrapper-exception. You can then access the original exception by calling e.getCause() and handle it accordingly.

EDIT - I'd make a few changes in your code with regard to exception handling. First, I'd use a Callable<Socket> instead of Callable<String> so you can easily use the socket later on, as you're sure that it's open and connected:

private Callable<Socket> waitForConnectionCallable = new Callable<>() {
    @Override
    public Socket call() throws IOException {
        if (socket == null) {
            socket = new ServerSocket(PORT);
            socket.setSoTimeout(CONNECTION_TIMEOUT);
        }
        return socket.accept(); // Can throw IOException or SocketTimeoutException
    }
};

Then, perform the exception handling in the waitForConnectionCallable.get():

try {
        Future<Socket> connectionFuture = executorService.submit(waitForConnectionCallable);
        Socket connectionResult = connectionFuture.get();
    } catch (InterruptedException e) {
        Thread.interrupted();
    } catch (ExecutionException e) { // IOException or SocketTimeoutException
        Throwable t = e.getCause();
        t.printStackTrace();
        if (t instanceof SocketTimeoutException) {
            Logs.error(TAG, "No connection received");
        } else { // IOException
            Logs.error(TAG, "Error waiting for connection: "+t.getMessage());
        }
    }

Or, you could perform the wait in the Callable, but then there might be no point in setting a socket timeout, as you're already using a different thread so you can stay in blocking mode:

private Callable<Socket> waitForConnectionCallable = new Callable<>() {
    @Override
    public Socket call() throws IOException {
        if (socket == null) {
            socket = new ServerSocket(PORT);
            socket.setSoTimeout(CONNECTION_TIMEOUT);
        }
        for (;;) { // Until we receive a connection
            try {
                return socket.accept(); // Can throw IOException or SocketTimeoutException
            } catch (SocketTimeoutException e) {
                // Do something? return null, log, continue, ...
            } // IOException is not caught here and will bubble up in the ExecutionException
        }
    }
};
Matthieu
  • 2,736
  • 4
  • 57
  • 87
2

There are few things to correct in your observations

Now, the java doc for setSoTimeout() mentions raising a SocketTimeoutException, though strangely it is not included in the Throws list

No, it says

Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a call to accept() for this ServerSocket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the ServerSocket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.

Which means that, if you set a timeout using the setSoTimeout(), a SocketTimeoutException will be thrown, if the timeout happens while accepting the connection. So SocketTimeoutException is not thrown by setSoTimeout(), hence its not in its throws list.

You can catch the timeout exception in the accept() method. It throws an IOException if something goes wrong in accepting the connection. Timeout can be one of the reasons.

Like below:

private Callable<String> waitForConnectionCallable = new Callable<String>() {

        @Override
        public String call() throws IOException {
            if (socket == null) {
                socket = new ServerSocket(PORT);
            }

            socket.setSoTimeout(CONNECTION_TIMEOUT);

            try {
                Socket inSocket = socket.accept();
            } catch (SocketTimeoutException e) {
                // do timeout handling...
                return "TIMEOUT";
            }

            return "CONNECTED";
        }
    };
Kris
  • 8,680
  • 4
  • 39
  • 67
  • Thank you, now I understand. I guess I also got confused by the IOException in `accept()` signature and didn't check it was that method throwing the SocketTimeoutException. Also, by "the ServerSocket is still valid", it means the socket is still open and I only need to call accept() again, right? – Mahm00d Oct 20 '21 at 11:18
  • yeah, it means the socket is not closed. – Kris Oct 20 '21 at 12:49
  • 1
    @Mahm00d you can call as many `accept()` as you want on an open `ServerSocket`. If you want to stop accepting connection and close the listening port on your computer, you should close the `ServeSocket`. – Matthieu Oct 21 '21 at 06:20
  • 1
    @Mahm00d, also note that `SocketTimeoutException` is a subclass of `IOException` so you might want to catch it *specifically* to handle the common timeout (recoverable), and catch the `IOException` which usually is *not* recoverable. – Matthieu Oct 21 '21 at 06:22