8

If client socket opens before the server socket, Java will generate a ConnectionException. So I have to check whether the server is available and keep waiting before executing

socketChannel.open(hostname, port)

in client thread. I've found an related API:

InetAddress.getByName(hostname).isReachable()

However, this still can't tell whether the socket on a specific port is open. I think this problem should be common but I didn't get very useful information from Google and other places.

qweruiop
  • 3,156
  • 6
  • 31
  • 55

3 Answers3

14
boolean scanning=true;
while(scanning) {
    try {
        socketChannel.open(hostname, port);
        scanning=false;
    } catch(ConnectionException e) {
        System.out.println("Connect failed, waiting and trying again");
        try {
            Thread.sleep(2000);//2 seconds
        } catch(InterruptedException ie){
            ie.printStackTrace();
        }
    } 
}

This is the code for sonics comment

Cruncher
  • 7,641
  • 1
  • 31
  • 65
  • +1 You can keep trying to connect until successful, or you give up. – Peter Lawrey Sep 04 '13 at 14:50
  • 1
    @PeterLawrey a failure counter of some kind I guess would be good, or allow scanning to be accessed by public methods so that another thread can stop this – Cruncher Sep 04 '13 at 14:51
  • Only if you want to give up after X attempts. A timeout is more common. I wouldn't give up if it's connection you need on a server to function. – Peter Lawrey Sep 04 '13 at 14:53
  • You can see an example with a timeout in FitNesse, which starts up a "slim" server from the command-line and then polls for a connection on the client side: https://github.com/unclebob/fitnesse/blob/30b496e330add41ab36b7fa04b21f1a6e8fefecd/src/fitnesse/testsystems/slim/SlimCommandRunningClient.java#L87 – drrob Mar 22 '18 at 09:17
  • Instead of a retry counter, you might use a "stopwatch" - get the time at the start of the retry loop, and fail the loop after a time delta is exceeded. This gives a nice "total timeout" semantic that doesn't depend on doing arithmetic (i.e. max retries * sleep per try) AND it properly marks time when the sleeps are interrupted. Of course, you can combine the two limits, but then the max-count aspect is more to catch local interruption noise with the stopwatch being the actual "wait this long for the service" bit. – Stevel Dec 14 '20 at 15:06
  • Took too long to write my answer before coming back to edit my comment... (Doesn't that suck, UX-wise??). ANYway, if you like my comment (above), then you may also enjoy my own answer, which may be below this one (until YOU up-vote it, if you think it worthy). – Stevel Dec 14 '20 at 16:46
4

I Will give this kind of handler for your client, I am using it in my little game.

It will also give up after few times.

private static int MAX_CONNECTION = 10;
private int reconnections = 0;

public void connect() {

            try {
                    this.socket = new Socket();
                    InetSocketAddress sa = new InetSocketAddress(this.server, this.port);
                    this.socket.connect(sa,500);
                    this.connected = true;

                    this.in = new InputStreamReader(this.socket.getInputStream());
                    this.out = new OutputStreamWriter(this.socket.getOutputStream());
            } catch (ConnectException e) {
                    System.out.println("Error while connecting. " + e.getMessage());
                    this.tryToReconnect();
            } catch (SocketTimeoutException e) {
                    System.out.println("Connection: " + e.getMessage() + ".");
                    this.tryToReconnect();
            } catch (IOException e) {
                    e.printStackTrace();
            }

    }
private void tryToReconnect() {
            this.disconnect();

            System.out.println("I will try to reconnect in 10 seconds... (" + this.reconnections + "/10)");
            try {
                    Thread.sleep(10000); //milliseconds
            } catch (InterruptedException e) {
            }

            if (this.reconnections < MAX_RECONNECTIONS) {
                    this.reconnections++;
                    this.connect();

            } else {
                    System.out.println("Reconnection failed, exeeded max reconnection tries. Shutting down.");
                    this.disconnect();
                    System.exit(0);
                    return;
            }

    }

Here is the explanation of the code:

private static final int MAX_CONNECTION = 10;
private int reconnections = 0;

First I declare two vars, one is fixed and cannot be changed at runtime, it's the maximum number of attempt I want the client to do before shutting down. The second is the current reconnection attempt.

public method connect() is used to connect the socket. I will skip to the exception handling:

} catch (ConnectException e) {
    System.out.println("Error while connecting. " + e.getMessage());
    this.tryToReconnect();
} catch (SocketTimeoutException e) {
    System.out.println("Connection: " + e.getMessage() + ".");
    this.tryToReconnect();
}

When a connection exception is thrown the catcher call the reconnection method.

The reconnection method wait 10 second between each attempt and is called each time by connect() if this fail.

If the connection is established connect() will not call tryToReconnect() again. If is impossible to connect within 100 seconds 10 attempts one every 10 second, the program exit.

Gianmarco
  • 2,536
  • 25
  • 57
  • 2
    Recursion is not an appropriate technique here. Just loop in the original method. – user207421 Sep 04 '13 at 18:26
  • why you think is better a loop? in this way if a disconnection happen I can restart the count of 10 from 0, just putting a `reconnections = 0` in the connect(). If I use a loop the loop, if connected it will be more difficult to make the reconnection... – Gianmarco Sep 04 '13 at 21:24
  • @EJP It's hardly recursion. All of the recursive calls are at the tail their respective methods. The big problem here is that all of the reconnections stay on the stack until you connect. if MAX_CONNECTIONS is big enough, namely about half the size of max stack, then you will get max recursion depth. It's really being used like a goto, but eats the stack. Goto's evil twin. – Cruncher Sep 05 '13 at 12:58
  • I'm afraid this code will throw an unhandle exception which is "StackoverflowError". Just like what EJP said, recusion is not appropriate here. It's better to loop in the original method. – Polar Jan 25 '18 at 01:36
  • I concur with the "recursion is not the best approach here" assessment. Unless you are fundamentally recursion minded, it's not worth the complexity and is not needed at all. AND, if you have many, many retries (e.g. very high MAX_CONNECTIONS with a server that is just not there), you really can exhaust the thread's stack. Definitely go with @Cruncher's answer if you like a "retry count" approach, or mine if you prefer a "max wait time" way. (And upvote which you prefer.) – Stevel Dec 14 '20 at 16:53
1

I came to this convo in search of an answer, then I made a "how about this way" comment. Now, it seems fair and community minded to share my own answer, copy & pasted unchanged from my own working (green unit tested) code:

private void makeSocketWithRetries(long maxWaitMS) throws IOException {
    Stopwatch retryWatch = new Stopwatch().reset();
    final long retryWaitMS = 500;

    do {
        try {
            socket = new Socket(host, port);
        }
        catch (ConnectException e) {
            logger.info("failed to connect tp [" + host + ":" + port + "]: retrying in " + retryWaitMS + "(ms)");
            try {
                Thread.sleep(retryWaitMS);
            } catch (InterruptedException interruptedException) {
                // swallow this exception
            }
        }
    }
    while ((socket == null) && (retryWatch.mark().deltaMS() <= maxWaitMS));

    if (socket == null) {
        String msg = "failed to connect tp [" + host + ":" + port + "] total wait time exceeded: " + retryWatch.deltaMS() + "(ms)";
        logger.info(msg);
        throw new ConnectException(msg);
    }
}

That being "said," here are a few notes:

  1. The logging is a bit much, but it shows what's going on. Trace logging makes more sense for the retries and maybe also for the "time exceeded" exception. Or just trash the logging altogether.
  2. retryWait should be a class constant (or a parameter with validations / range caps / log.warning if very large / etc.) At least it's a local final #8-)__
  3. My Stopwatch utility class is not shown, but it should be hopfefully is self-evident.
  4. No parameter checking, although a negative maxWaitMS wait will still make at least a single attempt.

ALSO, I don't count (nor limit) the number of retries. As per my comment to @Cruncher's solution, I don't believe it's generally a helpful feature.

Stevel
  • 631
  • 5
  • 13