nv-websocket-client icon indicating copy to clipboard operation
nv-websocket-client copied to clipboard

Fire reconnect event in any case.

Open akoidan opened this issue 7 years ago • 3 comments

I'm developing an android application that listens for server events via websockets and I've been struggling with one issue. I need to reconnect to websocket if connection has failed or was broken. So here's the situation.

  1. WS server is down. (or just no internet)
  2. I create factory with setConnectionTimeout to 0.
  3. I create new websocket, add a new listener to it and call connectAsynchronously.
  4. My listener calls reconnect in onDisconnected method as you described. You also noted:

Note that you should not trigger reconnection in onError() method because onError() may be called multiple times due to one error. Instead, onDisconnected() is the right place to trigger reconnection.

  1. After some timeout onConnectError event is fired on my listener. com.neovisionaries.ws.client.WebSocketException: Failed to connect to '192.168.1.2:5001': failed to connect to /192.168.1.2 (port 5001): connect failed: ETIMEDOUT (Connection timed out)
  2. My server goes online (or just internet goes online). And my socket won't connect to it.

I also noted that onConnectError is triggered only once. Is it safe to place reconnect there? Should I be aware of some other situations? How can I differ timedout event from any else, is there an error code in WebSocketException (or just String.contains)? From my perspective of view it would be nice to have WebSocket factory autoreconnect parameter or something like that, that handles this situation.

Could you please suggest me the best way to resolve my situation. Also could you please add some brief description to README.md for autoreconnect situation.

akoidan avatar Dec 24 '16 11:12 akoidan

I am not sure if there's any automatic reconnect solution. I am using simple repeating scheduler to check every 5 seconds if websocket instance is properly connected. I don't see any performance or battery issue with this approach and it works quite reliably.

Here is some code to help you get started:

Firstly I define the executor thread:

private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

.... then I call this in my repository class constructor:

 `scheduler.scheduleAtFixedRate(new Runnable() {
        public void run() {

            if (!state.isNetworkAvailable()) {
                return;
            }

            if (!state.isRepositoryConnected()) try {
                ws = createWebSocket(); // method to create a new ws instance
                ws.connect();
            } catch (Exception e) {
                state.newWssRepositoryState(WebSocketState.CLOSED); 
                state.newUserState(RepositoryState.UserState.NOT_LOGGED_IN);
            }
        }
    }, 0, 5, TimeUnit.SECONDS);`

PS: state is just my helper object to encapsulate the state of websocket, user and the network.

martinvano avatar Dec 25 '16 00:12 martinvano

@Deathangel908 As JavaDoc says, WebSocketListener.onConnectError() is called only when WebSocket.connectAsynchronously() was used and the WebSocket.connect() executed in the background thread failed. It will be safe to trigger reconnection in onConnectError().

I should have mentioned it explicitly, but the reason I use an expression of "trigger reconnection" instead of "call reconnect()" is that I myself won't call recreate().connect() synchronously in WebSocketListener callback methods but will just schedule reconnection, or will just go to the top of a kind of application loop that repeats to establish a WebSocket connection until it succeeds.

As an example, a certain application I once developed defines WebSocketListener.onStateChanged() like below.

@Override
public void onStateChanged(WebSocket websocket, WebSocketState newState)
{
    synchronized (mWebSocketLock)
    {
        mWebSocketState = newState;

        if (newState == CLOSED)
        {
            // Wake up waitForWebSocketToBeClosed().
            mWebSocketLock.notifyAll();
        }
    }
}

and waitForWebSocketToBeClosed() is defined like below.

private boolean waitForWebSocketToBeClosed()
{
    try
    {
        // Wait for the state of the WebSocket instance to change.
        long timeout = ...;
        mWebSocketLock.wait(timeout);
    }
    catch (InterruptedException e)
    {
    }

    return (mWebSocketState == CLOSED);
}

And a method to prepare a WebSocket connection is written like below.

private boolean prepareWebSocket()
{
    synchronized (mWebSocketLock)
    {
        // If there exists a WebSocket instance.
        if (mWebSocketState != null)
        {
            // Dispatch based on the current state of the WebSocket instance.
            switch (mWebSocketState)
            {
                case CREATED:
                    // This never happens because mWebSocketState remains CREATED
                    // only when the call of shouldStop() method right before
                    // 'connect()' (see the code below) returns true. Note that
                    // in such a case, this application loop stops and never
                    // reaches here.
                    return false;

                case CONNECTING:
                    // This never happens because a WebSocket instance takes
                    // this state only during the process of 'connect()' which
                    // is a synchronous method.
                    return false;

                case OPEN:
                    // The WebSocket instance is usable.
                    return true;

                case CLOSING:
                    // Wait for the state to become CLOSED.
                    if (waitForWebSocketToBeClosed() == false)
                    {
                        // Waited for a while, but the state did not become CLOSED.
                        return false;
                    }

                    // Now the state of the WebSocket instance is CLOSED.
                    break;

                case CLOSED:
                    // Need to create a new WebSocket instance.
                    break;
            }
        }

        // Create a WebSocket instance.
        WebSocket webSocket = createWebSocket();

        // If a WebSocket instance failed to be created.
        if (webSocket == null)
        {
            // Failed to create a WebSocket instance.
            return false;
        }

        // Created a WebSocket instance.
        mWebSocket      = webSocket;
        mWebSocketState = CREATED;
    }

    // Before calling 'connect()' method, check the state of mShouldStop.
    if (shouldStop())
    {
        // This application loop should stop.
        return false;
    }

    try
    {
        // Connect to the server and perform an opening handshake.
        // This is a synchronous method.
        // mWebSocketState is updated in onStateChanged.
        mWebSocket.connect();
    }
    catch (WebSocketException e)
    {
        // Failed to open a WebSocket connection.
        return false;
    }

    // A WebSocket connection has been opened successfully.
    return true;
}

A method to call prepareWebSocket() with back-off considered would look like this.

private boolean ensureWebSocket()
{
    if (mWebSocketBackOff != null)
    {
        // Wait before retrying to open a WebSocket connection.
        if (doWebSocketBackOff() == false)
        {
            // mShouldStop is true.
            return false;
        }
    }

    // Prepare a WebSocket.
    boolean prepared = prepareWebSocket();

    // If successfully prepared.
    if (prepared)
    {
        // WebSocket connection is now available.
        mWebSocketBackOff      = null;
        mWebSocketBackOffCount = 0;
        return true;
    }

    if (mWebSocketBackOff == null)
    {
        // Create a backoff to compute the delay before the next trial.
        mWebSocketBackOff = new WebSocketBackOff();
    }

    // WebSocket connection is not available. Retry.
    return false;
}

Well, in general, it's hard to write robust code to keep a network connection being established. State changes have to be treated very carefully.

TakahikoKawasaki avatar Dec 25 '16 11:12 TakahikoKawasaki

@TakahikoKawasaki your example above is very helpful. Would you consider including a complete example app with this library? I'm sure a lot of people including myself would benefit from a verified example of a retrying websocket client using your library as you recommend. The current examples are helpful at a introductory level, but I came to this issue with a similar question about how to correctly and robustly reconnect. Thanks!

proximous avatar Feb 23 '17 00:02 proximous