美文网首页优质文章
WebSocket连接(含重连机制)

WebSocket连接(含重连机制)

作者: 骚年一起奋斗吧 | 来源:发表于2019-01-24 11:42 被阅读478次

    首先大致的了解一下,我们为什么有时会用webSocket而不去用Socket呢?这是因为WebSocket是一个应用层协议。很多的东西都是规定好了,我们直接可以按照它的规定来用就可以了。而Socket是传输层和应用层的一个抽象层。里面有很多的东西我们得自己去规定。这样来说会显的相对的比较麻烦。

    这里我们不可能自己去实现一个webSocket协议,所有我们运用到一个框架nv-websocket-client

    在我的项目中,我因为对接的服务器是通过webSocket返回给我推送的信息。所以,我这里就是一个很简单的逻辑,只需要通过webSocket去连接服务器,服务器不停的给我推送数据。在此过程中做好异常情况的处理(断网情况)时进行重新连接。

    其实,在日常的开发中,我们在很多场景中也要实现这种重连的机制。比如说,一个用户登录流程

    接下来,我们就简单的来看一下,具体的实现

    1、引入webSocket的依赖

     implementation  'com.neovisionaries:nv-websocket-client:2.2'
    

    2、创建一个单例模式的WsManager,用来管理webSocket的全局调用

        private WsManager() {
        }
    
    
        public static WsManager getInstance() {
            if (mInstance == null) {
                synchronized (WsManager.class) {
                    if (mInstance == null) {
                        mInstance = new WsManager();
                    }
                }
            }
            return mInstance;
        }
    

    3、并在这个这个管理类里面添加建立连接的代码

    public class WsManager {
        private static WsManager mInstance;
        private final String TAG = this.getClass().getSimpleName();
    
        /**
         * WebSocket config
         */
        private static final int FRAME_QUEUE_SIZE = 5;
        private static final int CONNECT_TIMEOUT = 5000;
       //测试服默认地址
        private static final String DEF_TEST_URL = "ws://10.7.5.88:8089/gs-robot/notice/device_status";
       //正式服默认地址
        private static final String DEF_RELEASE_URL = "ws://10.7.5.88:8089/gs-robot/notice/device_status";
        private static final String DEF_URL = BuildConfig.DEBUG ? DEF_TEST_URL : DEF_RELEASE_URL;
        private String url;
    
        private WsStatus mStatus;
        private WebSocket ws;
        private WsListener mListener;
    
    
        private WsManager() {
        }
    
    
        public static WsManager getInstance() {
            if (mInstance == null) {
                synchronized (WsManager.class) {
                    if (mInstance == null) {
                        mInstance = new WsManager();
                    }
                }
            }
            return mInstance;
        }
    
    
        public void init() {
            try {
                /**
                 * configUrl其实是缓存在本地的连接地址
                 * 这个缓存本地连接地址是app启动的时候通过http请求去服务端获取的,
                 * 每次app启动的时候会拿当前时间与缓存时间比较,超过6小时就再次去服务端获取新的连接地址更新本地缓存
                 */
                String configUrl = "";
                url = TextUtils.isEmpty(configUrl) ? DEF_URL : configUrl;
                ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                    .setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
                    .setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
                    .addListener(mListener = new WsListener())//添加回调监听
                    .connectAsynchronously();//异步连接
                setStatus(WsStatus.CONNECTING);
                Logger.t(TAG).d("第一次连接");
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 继承默认的监听空实现WebSocketAdapter,重写我们需要的方法
         * onTextMessage 收到文字信息
         * onConnected 连接成功
         * onConnectError 连接失败
         * onDisconnected 连接关闭
         */
        class WsListener extends WebSocketAdapter {
            @Override
            public void onTextMessage(WebSocket websocket, String text) throws Exception {
                super.onTextMessage(websocket, text);
                Logger.t(TAG).d(text);
            }
    
    
            @Override
            public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
                throws Exception {
                super.onConnected(websocket, headers);
                Logger.t(TAG).d("连接成功");
                setStatus(WsStatus.CONNECT_SUCCESS);
                cancelReconnect();//连接成功的时候取消重连,初始化连接次数
            }
    
    
            @Override
            public void onConnectError(WebSocket websocket, WebSocketException exception)
                throws Exception {
                super.onConnectError(websocket, exception);
                Logger.t(TAG).d("连接错误");
                setStatus(WsStatus.CONNECT_FAIL);
                reconnect();//连接错误的时候调用重连方法
            }
    
    
            @Override
            public void onDisconnected(WebSocket websocket, 
    WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
                throws Exception {
                super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
                Logger.t(TAG).d("断开连接");
                setStatus(WsStatus.CONNECT_FAIL);
                reconnect();//连接断开的时候调用重连方法
            }
        }
    
    
        private void setStatus(WsStatus status) {
            this.mStatus = status;
        }
    
    
        private WsStatus getStatus() {
            return mStatus;
        }
    
    
        public void disconnect() {
            if (ws != null) {
                ws.disconnect();
            }
        }
    
    
        private Handler mHandler = new Handler();
    
        private int reconnectCount = 0;//重连次数
        private long minInterval = 3000;//重连最小时间间隔
        private long maxInterval = 60000;//重连最大时间间隔
    
    
        public void reconnect() {
            if (!isNetConnect()) {
                reconnectCount = 0;
                Logger.t(TAG).d("重连失败网络不可用");
                return;
            }
    
            //这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
            //由于我们这里是个demo所以省略了
            if (ws != null &&
                !ws.isOpen() &&//当前连接断开了
                getStatus() != WsStatus.CONNECTING) {//不是正在重连状态
    
                reconnectCount++;
                setStatus(WsStatus.CONNECTING);
    
                long reconnectTime = minInterval;
                if (reconnectCount > 3) {
                    url = DEF_URL;
                    long temp = minInterval * (reconnectCount - 2);
                    reconnectTime = temp > maxInterval ? maxInterval : temp;
                }
    
                Logger.t(TAG).d("准备开始第%d次重连,重连间隔%d -- url:%s", reconnectCount, reconnectTime, url);
                mHandler.postDelayed(mReconnectTask, reconnectTime);
            }
        }
    
    
        private Runnable mReconnectTask = new Runnable() {
    
            @Override
            public void run() {
                try {
                    ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                        .setFrameQueueSize(FRAME_QUEUE_SIZE)//设置帧队列最大值为5
                        .setMissingCloseFrameAllowed(false)//设置不允许服务端关闭连接却未发送关闭帧
                        .addListener(mListener = new WsListener())//添加回调监听
                        .connectAsynchronously();//异步连接
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
    
    
        private void cancelReconnect() {
            reconnectCount = 0;
            mHandler.removeCallbacks(mReconnectTask);
        }
    
    
        private boolean isNetConnect() {
            ConnectivityManager connectivity = (ConnectivityManager) WsApplication.getContext()
                .getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivity != null) {
                NetworkInfo info = connectivity.getActiveNetworkInfo();
                if (info != null && info.isConnected()) {
                    // 当前网络是连接的
                    if (info.getState() == NetworkInfo.State.CONNECTED) {
                        // 当前所连接的网络可用
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    
    public enum WsStatus {
        CONNECT_SUCCESS,//连接成功
        CONNECT_FAIL,//连接失败
        CONNECTING;//正在连接
    }
    

    上面的init()代表的就是建立连接的地方。里面添加了一个WsListener监听,是用来监听我们所需要的方法。

    同时我们维护着自己的一些连接状态。在reconnect()(重连)方法中,我们在连接断开,并且不是重连状态的情况下,进行重连操作。这里定义了一个最小重连时间间隔min和一个最大重连时间间隔max,当重连次数小于等于3次的时候,都是以最小的重连间隔时间去尝试重连。当重连次数大于3次的时候,我们将重连地址替换成默认地址DEF_URL,将重连时间间隔min*(重连次数-2)递增最大不超过max。还有就是在重连的时候,去判断当前的网络状态是否可用

    4、对于可用网络切换这里通过广播的监听来实现重连的

    public class NetStatusReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
    
                // 获取网络连接管理器
                ConnectivityManager connectivityManager
                    = (ConnectivityManager) WsApplication.getContext()
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
                // 获取当前网络状态信息
                NetworkInfo info = connectivityManager.getActiveNetworkInfo();
    
                if (info != null && info.isAvailable()) {
                    Logger.t("WsManager").d("监听到可用网络切换,调用重连方法");
                    WsManager.getInstance().reconnect();//wify 4g切换重连websocket
                }
    
            }
        }
    
    

    5、应用回到前台情况的重连
    通过Application.ActivityLifecycleCallbacks实现app前后台切换监听

    **
     * 前后台切换的监听
     */
    public class ForegroundCallbacks implements Application.ActivityLifecycleCallbacks {
    
        public static final long CHECK_DELAY = 600;
        public static final String TAG = ForegroundCallbacks.class.getName();
        private static ForegroundCallbacks instance;
        private boolean foreground = false, paused = true;
        private Handler handler = new Handler();
        private List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
        private Runnable check;
    
        public static ForegroundCallbacks init(Application application) {
            if (instance == null) {
                instance = new ForegroundCallbacks();
                application.registerActivityLifecycleCallbacks(instance);
            }
            return instance;
        }
    
        public static ForegroundCallbacks get(Application application) {
            if (instance == null) {
                init(application);
            }
            return instance;
        }
    
        public static ForegroundCallbacks get(Context ctx) {
            if (instance == null) {
                Context appCtx = ctx.getApplicationContext();
                if (appCtx instanceof Application) {
                    init((Application) appCtx);
                }
                throw new IllegalStateException(
                        "Foreground is not initialised and " +
                                "cannot obtain the Application object");
            }
            return instance;
        }
    
        public static ForegroundCallbacks get() {
    
            return instance;
        }
    
        public boolean isForeground() {
            return foreground;
        }
    
        public boolean isBackground() {
            return !foreground;
        }
    
        public void addListener(Listener listener) {
            listeners.add(listener);
        }
    
        public void removeListener(Listener listener) {
            listeners.remove(listener);
        }
    
        @Override
        public void onActivityResumed(Activity activity) {
            paused = false;
            boolean wasBackground = !foreground;
            foreground = true;
            if (check != null)
                handler.removeCallbacks(check);
            if (wasBackground) {
    
                for (Listener l : listeners) {
                    try {
                        l.onBecameForeground();
                    } catch (Exception exc) {
    
                    }
                }
            } else {
    
            }
        }
    
        @Override
        public void onActivityPaused(Activity activity) {
            paused = true;
    
            if (check != null)
                handler.removeCallbacks(check);
            handler.postDelayed(check = new Runnable() {
                @Override
                public void run() {
                    if (foreground && paused) {
                        foreground = false;
                        for (Listener l : listeners) {
                            try {
                                l.onBecameBackground();
                            } catch (Exception exc) {
    
                            }
                        }
                    } else {
    
                    }
                }
            }, CHECK_DELAY);
        }
    
        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }
    
        @Override
        public void onActivityStarted(Activity activity) {
        }
    
        @Override
        public void onActivityStopped(Activity activity) {
        }
    
        @Override
        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }
    
        @Override
        public void onActivityDestroyed(Activity activity) {
        }
    
        public interface Listener {
            public void onBecameForeground();
    
            public void onBecameBackground();
        }
    }
    

    6、最后在Application中初始化监听,当应用回到前台的时候尝试重连

    public class WsApplication extends Application {
    
    
        @Override
        public void onCreate() {
            super.onCreate();
            initAppStatusListener();
        }
    
        private void initAppStatusListener() {
            ForegroundCallbacks.init(this).addListener(new ForegroundCallbacks.Listener() {
                @Override
                public void onBecameForeground() {
                    Logger.t("WsManager").d("应用回到前台调用重连方法");
                    WsManager.getInstance().reconnect();
                }
    
                @Override
                public void onBecameBackground() {
    
                }
            });
        }
    }
    

    到这里连接的建立和重连讲完了,还剩客户端发送请求和服务端主动通知消息.下一次在说这个。

    源代码demoWebSocketReconnection

    相关文章

      网友评论

        本文标题:WebSocket连接(含重连机制)

        本文链接:https://www.haomeiwen.com/subject/yfhjjqtx.html