TooTallNate / Java-WebSocket

A barebones WebSocket client and server implementation written in 100% Java.
http://tootallnate.github.io/Java-WebSocket
MIT License
10.48k stars 2.58k forks source link

how to properly keep websocket alive #892

Closed denebchorny closed 5 years ago

denebchorny commented 5 years ago

NOTE: Android Question

Describe what you would like to know or do I simply want to keep the socket alive meanwhile the application is executing. This mean that if the socket die/disconnect/etc there have to be a way to wake it up.

As the internet connection is unstable at all (Fluctuates) and sometimes the socket execute the onClose at this point without reconneting I would like to know what to do.

Describe the solution you'd considered I was thinking in try to reconnect when onClose is executed (Ready State closing) but I receive this message:

" You cannot initialize a reconnect out of the websocket thread. Use reconnect in another thread to insure a successful cleanup.".

I dont know how to do it. Could you bring me an example?

IDK if this is the correct way. Imagine putting airplane mode in the phone. This is gonna make the socket execute onclose and trying to reconnect again as crazy. If this is the solution please help me to understand how to econnect in another thread. If dont, please give me an advice :(.

The idea is to avoid the multiple instances staying alive in every new connection.

Additional context If this help, this is MySocket class. I am trying to make it as singleton. `public class MySocket { private static final String TAG = MySocket.class.getSimpleName();

private static URI mServerUri;
private static WebSocketClient webSocketClient;

public static MySocket initSocket(String url) {
    return new MySocket(url);
}

public MySocket(String url) {
    if (webSocketClient == null) {
        URI uri = null;
        try {
            uri = new URI(url);
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        mServerUri = uri;

        if (mServerUri != null) {
            createWebSocket();
        }
    } else {
        reconnectIfNecessary();
    }
}

public synchronized void reconnectIfNecessary() {
    if (webSocketClient == null) {
        Log.e(TAG + " socket", "Null WebSocket, recreating");
        createWebSocket();
    } else if (webSocketClient.isClosed() || webSocketClient.isClosing()) {
        Log.e(TAG + " socket", "Ready State: "+webSocketClient.getReadyState());
        try {
            webSocketClient.reconnectBlocking();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } else {
        Log.e(TAG + " socket", "No reconnectIfNecessary: " + webSocketClient.getReadyState());
    }
}

private void createWebSocket() {
    SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();

    webSocketClient = new WebSocketClient(mServerUri) {
        @Override
        public void onOpen(ServerHandshake handshakedata) {
            Log.d(TAG + " socket", "onOpen");

            String token = Session.getAccessToken();
            JSONObject message = new JSONObject();
            JSONObject params = new JSONObject();
            try {
                message.put("action", "login_start");
                params.put("token", token);
                message.put("params", params);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            send(message.toString());
        }

        @Override
        public void onMessage(String message) {

        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            reconnectIfNecessary();
        }

        @Override
        public void onError(Exception ex) {
            //reconnectIfNecessary();
        }
    };

    webSocketClient.setSocketFactory(socketFactory);
    try {
        webSocketClient.connectBlocking();
    } catch (InterruptedException e) {
        e.printStackTrace();
        Log.e(TAG + " socket", "ERROR CONNECTING: "+e.getMessage());
    }
}

public void send(String msg) {
    if (isOpen()) {
        webSocketClient.send(msg);
    } else {
        Log.e(TAG + " socket", "Can't send message. WebSocket is null or closed");
    }
}

public boolean isOpen() {
    return webSocketClient != null && webSocketClient.isOpen();
}

public boolean isClosed() {
    return webSocketClient != null && webSocketClient.isClosed();
}

}`

marci4 commented 5 years ago

Hello,

the app has do decide when to reconnect, the library simple cannot do this since this is a java library and therefore has no access to android apis.

Best regards, Marcel

denebchorny commented 5 years ago

Yes sir. My mistake was that I was trying to reconnect the socket when it's CLOSING. I was trying to understand and find a way to reconnect the socket when It closes.

I found the solution. If someone needs something similar I will paste the code.

To understand what is this:

For me there is not a stop condition. For this I just let the OS to kill the process once the app die.

If a stop condition is need it, it has to be added.

package com.hipcam.android.hipcam.core.sockets;

import android.content.Context;
import android.os.Handler;
import android.support.v4.util.Pair;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonElement;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;

import javax.net.ssl.SSLSocketFactory;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

public class MySocket {
    private static final String TAG = MySocket.class.getSimpleName();

    private static URI mServerUri;
    private static Gson gson;
    private final Context mContext;

    private static WebSocketClient webSocketClient;

    private static ObservableEmitter<Pair<String, JsonElement>> emit;
    private static ObservableOnSubscribe<Pair<String, JsonElement>> emisible = e -> emit = e;

    private static Observable<Pair<String, JsonElement>> observable;
    private static boolean tryReconnecting = false;
    private int mCountForReconnect = 0;

    public static MySocket initSocket(Context context, String url) {
        return new MySocket(context, url);
    }

    public MySocket(Context context, String url) {
        this.mContext = context;
        if (gson == null) {
            gson = new Gson();
        }

        if (observable == null) {
            observable = Observable.create(emisible);
        }

        if (webSocketClient == null) {
            URI uri = null;
            try {
                uri = new URI(url);
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }

            mServerUri = uri;

            if (mServerUri != null) {
                createWebSocket();
            }
        } else {
            reconnectIfNecessary();
        }
    }

    public Observable<Pair<String, JsonElement>> getObservableListener() {
        return observable
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread());
    }

    public synchronized void reconnectIfNecessary() {
        if (tryReconnecting) {
            return;
        }
        /*ESTADOS
         *
         * ReadyState.NOT_YET_CONNECTED
         * ReadyState.CLOSED
         * ReadyState.CLOSING
         * ReadyState.OPEN
         *
         * */

        if (webSocketClient == null) {
            createWebSocket();

        } else if (!webSocketClient.isOpen()) {
            tryReconnecting = true;
            invocarDentroDelTimerEspecial();
        }
    }

    private void invocarDentroDelTimerEspecial() {
        int delay;
        if (webSocketClient == null) {
            createWebSocket();
            delay = 1;
            mCountForReconnect = 1;
        } else if (webSocketClient.isClosed()) {
            webSocketClient.reconnect();
            mCountForReconnect++;
            delay = getCurrentFibonacci(mCountForReconnect);
        } else if (webSocketClient.isOpen()) {
            tryReconnecting = false;
            mCountForReconnect = 1;
            delay = 0;
        } else {
            mCountForReconnect++;
            delay = getCurrentFibonacci(mCountForReconnect);
        }

        if (delay != 0) {
            handler.postDelayed(runnableTimer, delay * 1000);
        }
    }

    private void createWebSocket() {
        SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();

        webSocketClient = new WebSocketClient(mServerUri) {
            @Override
            public void onOpen(ServerHandshake handshakedata) {
                // TODO your stuff when socket opens
            }

            @Override
            public void onMessage(String message) {
                Log.d(TAG + " socket", "onMessage" + message);

                // TODO your stuff when message received
            }

            @Override
            public void onClose(int code, String reason, boolean remote) {
                reconnectIfNecessary();
            }

            @Override
            public void onError(Exception ex) {
                reconnectIfNecessary();
            }
        };

        webSocketClient.setSocketFactory(socketFactory);
        webSocketClient.connect();
    }

    public void send(String msg) {
        if (isOpen()) {
            webSocketClient.send(msg);
        }
    }

    public boolean isOpen() {
        return webSocketClient != null && webSocketClient.isOpen();
    }

    public boolean isClosed() {
        return webSocketClient != null && webSocketClient.isClosed();
    }

    public boolean isClosing() {
        return webSocketClient != null && webSocketClient.isClosing();
    }

    private final Handler handler = new Handler();
    private final Runnable runnableTimer = this::invocarDentroDelTimerEspecial;

    public static int getCurrentFibonacci(int item) {
        int maxNumber = 1000;
        int previousNumber = 0;
        int nextNumber = 1;

        int iterator=0;
        for (int i = 1; i <= maxNumber; ++i) {
            int sum = previousNumber + nextNumber;
            previousNumber = nextNumber;
            nextNumber = sum;
            if(iterator++ == item){
                return nextNumber;
            }
        }
        return maxNumber;
    }
}
marci4 commented 5 years ago

@denebchorny thank you for sharing your code! :)

kigkrazy commented 5 years ago

this is my solution, maybe it can help you.

EasyWs