精华内容
下载资源
问答
  • android长连接实现

    万次阅读 2017-12-12 11:08:35
    本文中我们将讲解一下App的长连接实现。一般而言长连接已经是App的标配了,推送功能的实现基础就是长连接,当然了我们也可以通过轮训操作实现推送功能,但是轮训一般及时性比较差,而且网络消耗与电量销毁比较多,...

    本文中我们将讲解一下App的长连接实现。一般而言长连接已经是App的标配了,推送功能的实现基础就是长连接,当然了我们也可以通过轮训操作实现推送功能,但是轮训一般及时性比较差,而且网络消耗与电量销毁比较多,因此一般推送功能都是通过长连接实现的。

    那么如何实现长连接呢?现在一般有这么几种实现方式:

    • 使用第三方的长连接服务;

    • 通过NIO等方案实现长连接服务;

    • 通过MINA等第三方框架实现长连接;

    几种长连接服务的具体实现,以及各自的优缺点。

    1. 使用第三方的长连接服务

    介绍:这是最简单的方式,我们可以通过接入极光推送,百度推送,友盟等第三方服务实现长连接,通过接入第三方的API我们可以很方便的接入第三方的长连接,推送服务,但是这种方式定制化程度不太好,如果对长连接服务不是要求特别高,对定制化要求不是很高的话基本可以考虑这种方式(目前主流的App都是使用第三方的长连接服务) 
    优势:简单,方便 
    劣势:定制化程度不高

    2. 使用NIO等方案实现长连接服务

    介绍:通过NIO的方式实现长连接,这种方式对技术要求程度比较高,基本都是通过java API实现长连接,实现心跳包,实现异常情况的容错等操作,可以说通过NIO实现长连接对技术要求很高,一般如果没有成行的技术方案比建议这么做,就算实现了长连接,后期连接的维护,对电量,流量的损耗等都需要持续的优化。 
    优势:定制化比较高 
    劣势:技术要求高,需要持续的维护

    3. 使用MINA等第三方框架实现长连接

    介绍:MINA是一个第三方的NIO框架,该框架实现了一整套的长连接机制,包括长连接的建立,心跳包的实现,异常机制的容错等。使用MINA实现长连接可以定制化的实现一些特有的功能,并且比NIO方案较为简单,因为其已经封装了一些长连接的特有机制,比如心跳包,容错等。 
    优势:可定制,较NIO方法简单 
    劣势:也需要一定的技术储备

    长连接具体实现

    在我们的Android客户端中长连接的实现机制采用–MINA方式。这里多说一句,一开始的长连接采用的是NIO方案,但是采用这种方案之后踩了很多坑,包括心跳,容错等机制都是自己写的,所以耗费了大量的时间,而且对手机电量的消耗很大,最后决定使用MINA NIO框架重新实现一遍长连接,后来经过实测,长连接的稳定性还有耗电量,流量的消耗等指标方面有了很大的提高。

    下面我将简单的介绍一下通过NIO实现长连接的具体流程:

    • 引入MINA jar包,在App启动页面,登录页面启动长连接;

    • 创建后台服务,在服务中创建MINA长连接;

    • 实现心跳包,重写一些容错机制;

    • 实现长连接断了之后的重连机制,并且重连次数有限制不能一直重连;

    • 长连接断了之后实现轮训操作,这里的轮训服务只有在长连接断了之后才启动,在长连接恢复之后关闭;

    以下就是在长连接中实现的具体代码:

    • 在Application的onCreate方法中检测App是否登录,若登录的话启动长连接
    /**
     * 在Application的onCreate方法中执行启动长连接的操作
     **/
    @Override
        public void onCreate() {
            ...
            // 登录后开启长连接
            if (UserConfig.isPassLogined()) {
                L.i("用户已登录,开启长连接...");
                startLongConn();
            }
            ...
        }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 通过闹钟服务实现具体的启动长连接service的操作,即每隔60秒钟判断长连接是否启动,若未启动则实现启动操作
        /**
         * 开始执行启动长连接服务
         */
        public void startLongConn() {
            quitLongConn();
            L.i("长连接服务已开启");
            AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(this, LongConnService.class);
            intent.setAction(LongConnService.ACTION);
            PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            long triggerAtTime = SystemClock.elapsedRealtime();
            manager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, 60 * 1000, pendingIntent);
        }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 下面的代码就是长连接服务的具体实现
    /**
     * 后台长连接服务
     **/
    public class LongConnService extends Service {
        public static String ACTION = "com.youyou.uuelectric.renter.Service.LongConnService";
        private static MinaLongConnectManager minaLongConnectManager;
        public String tag = "LongConnService";
        private Context context;
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            context = getApplicationContext();
            // 执行启动长连接的操作
            startLongConnect();
            ObserverManager.addObserver("LongConnService", stopListener);
            return START_STICKY;
        }
    
        public ObserverListener stopListener = new ObserverListener() {
            @Override
            public void observer(String from, Object obj) {
                closeConnect();
            }
        };
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            closeConnect();
        }
    
        /**
         * 开始执行启动长连接的操作
         */
        private void startLongConnect() {
            if (Config.isNetworkConnected(context)) {
                if (minaLongConnectManager != null && minaLongConnectManager.checkConnectStatus()) {
                    L.i("长连接状态正常...");
                    return;
                }
                if (minaLongConnectManager == null) {
                    startThreadCreateConnect();
                } else {
                    if (minaLongConnectManager.connectIsNull() && minaLongConnectManager.isNeedRestart()) {
                        L.i("session已关闭,需要重新创建一个session");
                        minaLongConnectManager.startConnect();
                    } else {
                        L.i("长连接已关闭,需要重开一个线程来重新创建长连接");
                        startThreadCreateConnect();
                    }
                }
            }
    
        }
    
        private final AtomicInteger mCount = new AtomicInteger(1);
    
        private void startThreadCreateConnect() {
            if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
                System.gc();
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 执行具体启动长连接操作
                        minaLongConnectManager = MinaLongConnectManager.getInstance(context);
                        minaLongConnectManager.crateLongConnect();
                    }
                }, "longConnectThread" + mCount.getAndIncrement()).start();
            }
        }
    
    
        private void closeConnect() {
    
            if (minaLongConnectManager != null) {
                minaLongConnectManager.closeConnect();
            }
            minaLongConnectManager = null;
    
            // 停止长连接服务LongConnService
            stopSelf();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 而下面的代码就是长连接的具体实现操作,具体的代码有相关注释说明
    /**
     * 具体实现长连接的管理对象
     **/
    public class MinaLongConnectManager {
    
        private static final String TAG = MinaLongConnectManager.class.getSimpleName();
        /**
         * 服务器端口号
         */
        public static final int DEFAULT_PORT = 18156;
        /**
         * 连接超时时间,30 seconds
         */
        public static final long SOCKET_CONNECT_TIMEOUT = 30 * 1000L;
    
        /**
         * 长连接心跳包发送频率,60s
         */
        public static final int KEEP_ALIVE_TIME_INTERVAL = 60;
        private static Context context;
        private static MinaLongConnectManager minaLongConnectManager;
    
        private static NioSocketConnector connector;
        private static ConnectFuture connectFuture;
        public static IoSession session;
        private static ExecutorService executorService = Executors.newSingleThreadExecutor();
    
        /**
         * 长连接是否正在连接中...
         */
        private static boolean isConnecting = false;
    
        private MinaLongConnectManager() {
            EventBus.getDefault().register(this);
        }
    
        public static synchronized MinaLongConnectManager getInstance(Context ctx) {
    
            if (minaLongConnectManager == null) {
                context = ctx;
                minaLongConnectManager = new MinaLongConnectManager();
            }
            return minaLongConnectManager;
        }
    
        /**
         * 检查长连接的各种对象状态是否正常,正常情况下无需再创建
         *
         * @return
         */
        public boolean checkConnectStatus() {
            if (connector != null && connector.isActive() && connectFuture != null && connectFuture.isConnected() && session != null && session.isConnected()) {
                return true;
            } else {
                return false;
            }
        }
    
        public boolean connectIsNull() {
            return connector != null;
        }
    
        /**
         * 创建长连接,配置过滤器链和心跳工厂
         */
        public synchronized void crateLongConnect() {
            // 如果是长连接正在创建中
            if (isConnecting) {
                L.i("长连接正在创建中...");
                return;
            }
            if (!Config.isNetworkConnected(context)) {
                L.i("检测到网络未打开,无法正常启动长连接,直接return...");
                return;
            }
            // 检查长连接的各种对象状态是否正常,正常情况下无需再创建
            if (checkConnectStatus()) {
                return;
            }
            isConnecting = true;
            try {
                connector = new NioSocketConnector();
                connector.setConnectTimeoutMillis(SOCKET_CONNECT_TIMEOUT);
    
                if (L.isDebug) {
                    if (!connector.getFilterChain().contains("logger")) {
                        // 设置日志输出工厂
                        connector.getFilterChain().addLast("logger", new LoggingFilter());
                    }
                }
                if (!connector.getFilterChain().contains("codec")) {
                    // 设置请求和响应对象的编解码操作
                    connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new LongConnectProtocolFactory()));
                }
                // 创建心跳工厂
                ClientKeepAliveMessageFactory heartBeatFactory = new ClientKeepAliveMessageFactory();
                // 当读操作空闲时发送心跳
                KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.READER_IDLE);
                // 设置是否将事件继续往下传递
                heartBeat.setForwardEvent(true);
                // 设置心跳包请求后超时无反馈情况下的处理机制,默认为关闭连接,在此处设置为输出日志提醒
                heartBeat.setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler.LOG);
                //设置心跳频率
                heartBeat.setRequestInterval(KEEP_ALIVE_TIME_INTERVAL);
                if (!connector.getFilterChain().contains("keepAlive")) {
                    connector.getFilterChain().addLast("keepAlive", heartBeat);
                }
                if (!connector.getFilterChain().contains("reconnect")) {
                    // 设置长连接重连过滤器,当检测到Session(会话)断开后,重连长连接
                    connector.getFilterChain().addLast("reconnect", new LongConnectReconnectionFilter());
                }
                // 设置接收和发送缓冲区大小
                connector.getSessionConfig().setReceiveBufferSize(1024);
                connector.getSessionConfig().setSendBufferSize(1024);
                // 设置读取空闲时间:单位为s
                connector.getSessionConfig().setReaderIdleTime(60);
    
                // 设置长连接业务逻辑处理类Handler
                LongConnectHandler longConnectHandler = new LongConnectHandler(this, context);
                connector.setHandler(longConnectHandler);
    
            } catch (Exception e) {
                e.printStackTrace();
                closeConnect();
            }
    
            startConnect();
        }
    
        /**
         * 开始或重连长连接
         */
        public synchronized void startConnect() {
            if (connector != null) {
                L.i("开始创建长连接...");
                boolean isSuccess = beginConnect();
                // 创建成功后,修改创建中状态
                if (isSuccess) {
                    isNeedRestart = false;
                    if (context != null) {
                        // 长连接启动成功后,主动拉取一次消息
                        LoopRequest.getInstance(context).sendLoopRequest();
                    }
                } else {
                    // 启动轮询服务
                    startLoopService();
                }
                isConnecting = false;
    //            printProcessorExecutor();
            } else {
                L.i("connector已为null,不能执行创建连接动作...");
            }
        }
    
        /**
         * 检测MINA中线程池的活动状态
         */
        private void printProcessorExecutor() {
            Class connectorClass = connector.getClass().getSuperclass();
            try {
                L.i("connectorClass:" + connectorClass.getCanonicalName());
                Field field = connectorClass.getDeclaredField("processor");
                field.setAccessible(true);
                Object connectorObject = field.get(connector);
                if (connectorObject != null) {
                    SimpleIoProcessorPool processorPool = (SimpleIoProcessorPool) connectorObject;
                    Class processPoolClass = processorPool.getClass();
                    Field executorField = processPoolClass.getDeclaredField("executor");
                    executorField.setAccessible(true);
                    Object executorObject = executorField.get(processorPool);
                    if (executorObject != null) {
                        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorObject;
                        L.i("线程池中当前线程数:" + threadPoolExecutor.getPoolSize() + "\t 核心线程数:" + threadPoolExecutor.getCorePoolSize() + "\t 最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
                    }
    
                } else {
                    L.i("connectorObject = null");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 开始创建Session
         *
         * @return
         */
        public boolean beginConnect() {
    
            if (session != null) {
                session.close(false);
                session = null;
            }
            if (connectFuture != null && connectFuture.isConnected()) {
                connectFuture.cancel();
                connectFuture = null;
            }
            FutureTask<Boolean> futureTask = new FutureTask<>(new Callable<Boolean>() {
                @Override
                public Boolean call() {
                    try {
                        InetSocketAddress address = new InetSocketAddress(NetworkTask.getBASEURL(), DEFAULT_PORT);
                        connectFuture = connector.connect(address);
                        connectFuture.awaitUninterruptibly(3000L);
                        session = connectFuture.getSession();
                        if (session == null) {
                            L.i(TAG + "连接创建失败...当前环境:" + NetworkTask.getBASEURL());
                            return false;
                        } else {
                            L.i(TAG + "长连接已启动,连接已成功...当前环境:" + NetworkTask.getBASEURL());
                            return true;
                        }
                    } catch (Exception e) {
                        return false;
                    }
                }
            });
    
            executorService.submit(futureTask);
            try {
                return futureTask.get();
            } catch (Exception e) {
                return false;
            }
    
        }
    
        /**
         * 关闭连接,根据传入的参数设置session是否需要重新连接
         */
        public synchronized void closeConnect() {
            if (session != null) {
                session.close(false);
                session = null;
            }
            if (connectFuture != null && connectFuture.isConnected()) {
                connectFuture.cancel();
                connectFuture = null;
            }
            if (connector != null && !connector.isDisposed()) {
                // 清空里面注册的所以过滤器
                connector.getFilterChain().clear();
                connector.dispose();
                connector = null;
            }
            isConnecting = false;
            L.i("长连接已关闭...");
        }
    
        private volatile boolean isNeedRestart = false;
    
        public boolean isNeedRestart() {
            return isNeedRestart;
        }
    
        public void onEventMainThread(BaseEvent event) {
            if (event == null || TextUtils.isEmpty(event.getType()))
                return;
            if (EventBusConstant.EVENT_TYPE_NETWORK_STATUS.equals(event.getType())) {
                String status = (String) event.getExtraData();
                // 当网络状态变化的时候请求startQuery接口
                if (status != null && status.equals("open")) {
                    if (isNeedRestart && UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
                        L.i("检测到网络已打开且长连接处于关闭状态,需要启动长连接...");
                        Intent intent = new Intent(context, LongConnService.class);
                        intent.setAction(LongConnService.ACTION);
                        context.startService(intent);
                    }
                }
            }
        }
    
        /**
         * 出现异常、session关闭后,接收事件进行长连接重连操作
         */
        public void onEventMainThread(LongConnectMessageEvent event) {
    
            if (event.getType() == LongConnectMessageEvent.TYPE_RESTART) {
    
                long currentTime = System.currentTimeMillis();
    
                // 票据有效的情况下进行重连长连接操作
                if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null
                        && ((currentTime / 1000) < UserConfig.getUserInfo().getUnvalidSecs())) {
                    // 等待2s后重新创建长连接
                    SystemClock.sleep(1000);
                    if (Config.isNetworkConnected(context)) {
                        L.i("出现异常情况,需要自动重连长连接...");
                        startConnect();
                    } else {
                        isNeedRestart = true;
                        L.i("长连接出现异常,需要重新创建session会话...");
                    }
                }
            } else if (event.getType() == LongConnectMessageEvent.TYPE_CLOSE) {
                L.i("收到session多次close的消息,此时需要关闭长连接,等待下次闹钟服务来启动...");
                closeConnect();
            }
        }
    
    
        /**
         * 启动轮询服务
         */
        public void startLoopService() {
            // 启动轮询服务
            // 暂时不考虑加入网络情况的判断...
            if (!LoopService.isServiceRuning) {
                // 用户是登录态,启动轮询服务
                if (UserConfig.isPassLogined()) {
                    // 判断当前长连接的状态,若长连接已连接,则不再开启轮询服务
                    if (MinaLongConnectManager.session != null && MinaLongConnectManager.session.isConnected()) {
                        LoopService.quitLoopService(context);
                        return;
                    }
                    LoopService.startLoopService(context);
                } else {
                    LoopService.quitLoopService(context);
                }
            }
        }
    
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325

    以上是通过NIO实现App长连接的部分核心代码,NIO中其实已经实现了长连接的核心流程,我们需要做的就是按照其流程实现长连接,需要注意的是要处理好异常情况,重连机制等。

    当长连接创建成功之后需要重新拉取一次服务器端的长连接消息,并且这里的长连接做了容错处理,当长连接断了之后需要有重连机制,一直启动轮训服务,当长连接修复之后轮训服务退出。以上只是通过MINA框架实现的长连接操作的核心流程,还有一些长连接实现的操作细节这里就不做过多的说明。

    总结: 
    基本上对于App来说长连接已经是标配了,产品开发人员可以根据具体的产品需求选择不同的实现方式,一般而言使用第三方的推送服务已经可以满足大部分的需求了,当然了若是相对技术有所追求的话也可以选择自己实现一套长连接服务,不过其中可能存在一些坑需要填,希望这里的长连接实现能够对大家对长连接实现上有所帮助。

    • 可以通过使用第三方长连接服务或者是自己实现连接的方式;

    • 自定义实现长连接可以通过使用NIO或者是第三方NIO框架,比如MINA实现;

    • 长连接实现中通过心跳包的机制实现App与服务器的长时间连接;

    • 可以通过闹钟的机制定时检测长连接服务是否可靠,长连接是否出现异常等;

    • 为了消息的及时性,在长连接出现异常情况时可通过创建轮训服务的机制实现对消息的获取,待长连接恢复之后关闭轮训服务;


    另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的: 
    Android产品研发(一)–>实用开发规范 
    Android产品研发(二)–>启动页优化 
    Android产品研发(三)–>基类Activity 
    Android产品研发(四)–>减小Apk大小 
    Android产品研发(五)–>多渠道打包 
    Android产品研发(六)–>Apk混淆 
    Android产品研发(七)–>Apk热修复 
    Android产品研发(八)–>App数据统计 
    Android产品研发(九)–>App网络传输协议 
    Android产品研发(十)–>不使用静态变量保存数据 
    Android产品研发(十一)–>应用内跳转scheme协议

    源自:http://blog.csdn.net/qq_23547831/article/details/51690047

    展开全文
  • Android产品研发(十二)-->App长连接实现

    万次阅读 多人点赞 2016-06-17 18:47:58
    本文中我们将讲解一下App的长连接实现。一般而言长连接已经是App的标配了,推送功能的实现基础就是长连接,当然了我们也可以通过轮训操作实现推送功能,但是轮训一般及时性比较差,而且网络消耗与电量销毁比较多,...

    转载请标明出处:一片枫叶的专栏

    上一篇文章中我们讲解了Android应用内页面跳转协议-scheme协议,通过该协议我们可以跳转至指定的Activity,并在该Activity中解析scheme用于跳转到指定的页面,我们可以利用scheme协议实现应用内页面跳转、H5页面与Native页面相互跳转、通知栏消息跳转相应页面等,具体可参考: Android产品研发(十一)–>使用scheme实现页面跳转

    而本文中我们将讲解一下App的长连接实现。一般而言长连接已经是App的标配了,推送功能的实现基础就是长连接,当然了我们也可以通过轮训操作实现推送功能,但是轮训一般及时性比较差,而且网络消耗与电量销毁比较多,因此一般推送功能都是通过长连接实现的。

    那么如何实现长连接呢?现在一般有这么几种实现方式:

    • 使用第三方的长连接服务;

    • 通过NIO等方案实现长连接服务;

    • 通过MINA等第三方框架实现长连接;

    几种长连接服务的具体实现,以及各自的优缺点。

    1. 使用第三方的长连接服务

    介绍:这是最简单的方式,我们可以通过接入极光推送,百度推送,友盟等第三方服务实现长连接,通过接入第三方的API我们可以很方便的接入第三方的长连接,推送服务,但是这种方式定制化程度不太好,如果对长连接服务不是要求特别高,对定制化要求不是很高的话基本可以考虑这种方式(目前主流的App都是使用第三方的长连接服务)
    优势:简单,方便
    劣势:定制化程度不高

    2. 使用NIO等方案实现长连接服务

    介绍:通过NIO的方式实现长连接,这种方式对技术要求程度比较高,基本都是通过java API实现长连接,实现心跳包,实现异常情况的容错等操作,可以说通过NIO实现长连接对技术要求很高,一般如果没有成行的技术方案比建议这么做,就算实现了长连接,后期连接的维护,对电量,流量的损耗等都需要持续的优化。
    优势:定制化比较高
    劣势:技术要求高,需要持续的维护

    3. 使用MINA等第三方框架实现长连接

    介绍:MINA是一个第三方的NIO框架,该框架实现了一整套的长连接机制,包括长连接的建立,心跳包的实现,异常机制的容错等。使用MINA实现长连接可以定制化的实现一些特有的功能,并且比NIO方案较为简单,因为其已经封装了一些长连接的特有机制,比如心跳包,容错等。
    优势:可定制,较NIO方法简单
    劣势:也需要一定的技术储备

    长连接具体实现

    在我们的Android客户端中长连接的实现机制采用–MINA方式。这里多说一句,一开始的长连接采用的是NIO方案,但是采用这种方案之后踩了很多坑,包括心跳,容错等机制都是自己写的,所以耗费了大量的时间,而且对手机电量的消耗很大,最后决定使用MINA NIO框架重新实现一遍长连接,后来经过实测,长连接的稳定性还有耗电量,流量的消耗等指标方面有了很大的提高。

    下面我将简单的介绍一下通过NIO实现长连接的具体流程:

    • 引入MINA jar包,在App启动页面,登录页面启动长连接;

    • 创建后台服务,在服务中创建MINA长连接;

    • 实现心跳包,重写一些容错机制;

    • 实现长连接断了之后的重连机制,并且重连次数有限制不能一直重连;

    • 长连接断了之后实现轮训操作,这里的轮训服务只有在长连接断了之后才启动,在长连接恢复之后关闭;

    以下就是在长连接中实现的具体代码:

    • 在Application的onCreate方法中检测App是否登录,若登录的话启动长连接
    /**
     * 在Application的onCreate方法中执行启动长连接的操作
     **/
    @Override
        public void onCreate() {
            ...
            // 登录后开启长连接
            if (UserConfig.isPassLogined()) {
                L.i("用户已登录,开启长连接...");
                startLongConn();
            }
            ...
        }
    • 通过闹钟服务实现具体的启动长连接service的操作,即每隔60秒钟判断长连接是否启动,若未启动则实现启动操作
        /**
         * 开始执行启动长连接服务
         */
        public void startLongConn() {
            quitLongConn();
            L.i("长连接服务已开启");
            AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(this, LongConnService.class);
            intent.setAction(LongConnService.ACTION);
            PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            long triggerAtTime = SystemClock.elapsedRealtime();
            manager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, 60 * 1000, pendingIntent);
        }
    • 下面的代码就是长连接服务的具体实现
    /**
     * 后台长连接服务
     **/
    public class LongConnService extends Service {
        public static String ACTION = "com.youyou.uuelectric.renter.Service.LongConnService";
        private static MinaLongConnectManager minaLongConnectManager;
        public String tag = "LongConnService";
        private Context context;
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            context = getApplicationContext();
            // 执行启动长连接的操作
            startLongConnect();
            ObserverManager.addObserver("LongConnService", stopListener);
            return START_STICKY;
        }
    
        public ObserverListener stopListener = new ObserverListener() {
            @Override
            public void observer(String from, Object obj) {
                closeConnect();
            }
        };
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            closeConnect();
        }
    
        /**
         * 开始执行启动长连接的操作
         */
        private void startLongConnect() {
            if (Config.isNetworkConnected(context)) {
                if (minaLongConnectManager != null && minaLongConnectManager.checkConnectStatus()) {
                    L.i("长连接状态正常...");
                    return;
                }
                if (minaLongConnectManager == null) {
                    startThreadCreateConnect();
                } else {
                    if (minaLongConnectManager.connectIsNull() && minaLongConnectManager.isNeedRestart()) {
                        L.i("session已关闭,需要重新创建一个session");
                        minaLongConnectManager.startConnect();
                    } else {
                        L.i("长连接已关闭,需要重开一个线程来重新创建长连接");
                        startThreadCreateConnect();
                    }
                }
            }
    
        }
    
        private final AtomicInteger mCount = new AtomicInteger(1);
    
        private void startThreadCreateConnect() {
            if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
                System.gc();
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        // 执行具体启动长连接操作
                        minaLongConnectManager = MinaLongConnectManager.getInstance(context);
                        minaLongConnectManager.crateLongConnect();
                    }
                }, "longConnectThread" + mCount.getAndIncrement()).start();
            }
        }
    
    
        private void closeConnect() {
    
            if (minaLongConnectManager != null) {
                minaLongConnectManager.closeConnect();
            }
            minaLongConnectManager = null;
    
            // 停止长连接服务LongConnService
            stopSelf();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            throw new UnsupportedOperationException("Not yet implemented");
        }
    }
    • 而下面的代码就是长连接的具体实现操作,具体的代码有相关注释说明
    /**
     * 具体实现长连接的管理对象
     **/
    public class MinaLongConnectManager {
    
        private static final String TAG = MinaLongConnectManager.class.getSimpleName();
        /**
         * 服务器端口号
         */
        public static final int DEFAULT_PORT = 18156;
        /**
         * 连接超时时间,30 seconds
         */
        public static final long SOCKET_CONNECT_TIMEOUT = 30 * 1000L;
    
        /**
         * 长连接心跳包发送频率,60s
         */
        public static final int KEEP_ALIVE_TIME_INTERVAL = 60;
        private static Context context;
        private static MinaLongConnectManager minaLongConnectManager;
    
        private static NioSocketConnector connector;
        private static ConnectFuture connectFuture;
        public static IoSession session;
        private static ExecutorService executorService = Executors.newSingleThreadExecutor();
    
        /**
         * 长连接是否正在连接中...
         */
        private static boolean isConnecting = false;
    
        private MinaLongConnectManager() {
            EventBus.getDefault().register(this);
        }
    
        public static synchronized MinaLongConnectManager getInstance(Context ctx) {
    
            if (minaLongConnectManager == null) {
                context = ctx;
                minaLongConnectManager = new MinaLongConnectManager();
            }
            return minaLongConnectManager;
        }
    
        /**
         * 检查长连接的各种对象状态是否正常,正常情况下无需再创建
         *
         * @return
         */
        public boolean checkConnectStatus() {
            if (connector != null && connector.isActive() && connectFuture != null && connectFuture.isConnected() && session != null && session.isConnected()) {
                return true;
            } else {
                return false;
            }
        }
    
        public boolean connectIsNull() {
            return connector != null;
        }
    
        /**
         * 创建长连接,配置过滤器链和心跳工厂
         */
        public synchronized void crateLongConnect() {
            // 如果是长连接正在创建中
            if (isConnecting) {
                L.i("长连接正在创建中...");
                return;
            }
            if (!Config.isNetworkConnected(context)) {
                L.i("检测到网络未打开,无法正常启动长连接,直接return...");
                return;
            }
            // 检查长连接的各种对象状态是否正常,正常情况下无需再创建
            if (checkConnectStatus()) {
                return;
            }
            isConnecting = true;
            try {
                connector = new NioSocketConnector();
                connector.setConnectTimeoutMillis(SOCKET_CONNECT_TIMEOUT);
    
                if (L.isDebug) {
                    if (!connector.getFilterChain().contains("logger")) {
                        // 设置日志输出工厂
                        connector.getFilterChain().addLast("logger", new LoggingFilter());
                    }
                }
                if (!connector.getFilterChain().contains("codec")) {
                    // 设置请求和响应对象的编解码操作
                    connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new LongConnectProtocolFactory()));
                }
                // 创建心跳工厂
                ClientKeepAliveMessageFactory heartBeatFactory = new ClientKeepAliveMessageFactory();
                // 当读操作空闲时发送心跳
                KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.READER_IDLE);
                // 设置是否将事件继续往下传递
                heartBeat.setForwardEvent(true);
                // 设置心跳包请求后超时无反馈情况下的处理机制,默认为关闭连接,在此处设置为输出日志提醒
                heartBeat.setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler.LOG);
                //设置心跳频率
                heartBeat.setRequestInterval(KEEP_ALIVE_TIME_INTERVAL);
                if (!connector.getFilterChain().contains("keepAlive")) {
                    connector.getFilterChain().addLast("keepAlive", heartBeat);
                }
                if (!connector.getFilterChain().contains("reconnect")) {
                    // 设置长连接重连过滤器,当检测到Session(会话)断开后,重连长连接
                    connector.getFilterChain().addLast("reconnect", new LongConnectReconnectionFilter());
                }
                // 设置接收和发送缓冲区大小
                connector.getSessionConfig().setReceiveBufferSize(1024);
                connector.getSessionConfig().setSendBufferSize(1024);
                // 设置读取空闲时间:单位为s
                connector.getSessionConfig().setReaderIdleTime(60);
    
                // 设置长连接业务逻辑处理类Handler
                LongConnectHandler longConnectHandler = new LongConnectHandler(this, context);
                connector.setHandler(longConnectHandler);
    
            } catch (Exception e) {
                e.printStackTrace();
                closeConnect();
            }
    
            startConnect();
        }
    
        /**
         * 开始或重连长连接
         */
        public synchronized void startConnect() {
            if (connector != null) {
                L.i("开始创建长连接...");
                boolean isSuccess = beginConnect();
                // 创建成功后,修改创建中状态
                if (isSuccess) {
                    isNeedRestart = false;
                    if (context != null) {
                        // 长连接启动成功后,主动拉取一次消息
                        LoopRequest.getInstance(context).sendLoopRequest();
                    }
                } else {
                    // 启动轮询服务
                    startLoopService();
                }
                isConnecting = false;
    //            printProcessorExecutor();
            } else {
                L.i("connector已为null,不能执行创建连接动作...");
            }
        }
    
        /**
         * 检测MINA中线程池的活动状态
         */
        private void printProcessorExecutor() {
            Class connectorClass = connector.getClass().getSuperclass();
            try {
                L.i("connectorClass:" + connectorClass.getCanonicalName());
                Field field = connectorClass.getDeclaredField("processor");
                field.setAccessible(true);
                Object connectorObject = field.get(connector);
                if (connectorObject != null) {
                    SimpleIoProcessorPool processorPool = (SimpleIoProcessorPool) connectorObject;
                    Class processPoolClass = processorPool.getClass();
                    Field executorField = processPoolClass.getDeclaredField("executor");
                    executorField.setAccessible(true);
                    Object executorObject = executorField.get(processorPool);
                    if (executorObject != null) {
                        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorObject;
                        L.i("线程池中当前线程数:" + threadPoolExecutor.getPoolSize() + "\t 核心线程数:" + threadPoolExecutor.getCorePoolSize() + "\t 最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
                    }
    
                } else {
                    L.i("connectorObject = null");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 开始创建Session
         *
         * @return
         */
        public boolean beginConnect() {
    
            if (session != null) {
                session.close(false);
                session = null;
            }
            if (connectFuture != null && connectFuture.isConnected()) {
                connectFuture.cancel();
                connectFuture = null;
            }
            FutureTask<Boolean> futureTask = new FutureTask<>(new Callable<Boolean>() {
                @Override
                public Boolean call() {
                    try {
                        InetSocketAddress address = new InetSocketAddress(NetworkTask.getBASEURL(), DEFAULT_PORT);
                        connectFuture = connector.connect(address);
                        connectFuture.awaitUninterruptibly(3000L);
                        session = connectFuture.getSession();
                        if (session == null) {
                            L.i(TAG + "连接创建失败...当前环境:" + NetworkTask.getBASEURL());
                            return false;
                        } else {
                            L.i(TAG + "长连接已启动,连接已成功...当前环境:" + NetworkTask.getBASEURL());
                            return true;
                        }
                    } catch (Exception e) {
                        return false;
                    }
                }
            });
    
            executorService.submit(futureTask);
            try {
                return futureTask.get();
            } catch (Exception e) {
                return false;
            }
    
        }
    
        /**
         * 关闭连接,根据传入的参数设置session是否需要重新连接
         */
        public synchronized void closeConnect() {
            if (session != null) {
                session.close(false);
                session = null;
            }
            if (connectFuture != null && connectFuture.isConnected()) {
                connectFuture.cancel();
                connectFuture = null;
            }
            if (connector != null && !connector.isDisposed()) {
                // 清空里面注册的所以过滤器
                connector.getFilterChain().clear();
                connector.dispose();
                connector = null;
            }
            isConnecting = false;
            L.i("长连接已关闭...");
        }
    
        private volatile boolean isNeedRestart = false;
    
        public boolean isNeedRestart() {
            return isNeedRestart;
        }
    
        public void onEventMainThread(BaseEvent event) {
            if (event == null || TextUtils.isEmpty(event.getType()))
                return;
            if (EventBusConstant.EVENT_TYPE_NETWORK_STATUS.equals(event.getType())) {
                String status = (String) event.getExtraData();
                // 当网络状态变化的时候请求startQuery接口
                if (status != null && status.equals("open")) {
                    if (isNeedRestart && UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null) {
                        L.i("检测到网络已打开且长连接处于关闭状态,需要启动长连接...");
                        Intent intent = new Intent(context, LongConnService.class);
                        intent.setAction(LongConnService.ACTION);
                        context.startService(intent);
                    }
                }
            }
        }
    
        /**
         * 出现异常、session关闭后,接收事件进行长连接重连操作
         */
        public void onEventMainThread(LongConnectMessageEvent event) {
    
            if (event.getType() == LongConnectMessageEvent.TYPE_RESTART) {
    
                long currentTime = System.currentTimeMillis();
    
                // 票据有效的情况下进行重连长连接操作
                if (UserConfig.getUserInfo().getB3Key() != null && UserConfig.getUserInfo().getSessionKey() != null
                        && ((currentTime / 1000) < UserConfig.getUserInfo().getUnvalidSecs())) {
                    // 等待2s后重新创建长连接
                    SystemClock.sleep(1000);
                    if (Config.isNetworkConnected(context)) {
                        L.i("出现异常情况,需要自动重连长连接...");
                        startConnect();
                    } else {
                        isNeedRestart = true;
                        L.i("长连接出现异常,需要重新创建session会话...");
                    }
                }
            } else if (event.getType() == LongConnectMessageEvent.TYPE_CLOSE) {
                L.i("收到session多次close的消息,此时需要关闭长连接,等待下次闹钟服务来启动...");
                closeConnect();
            }
        }
    
    
        /**
         * 启动轮询服务
         */
        public void startLoopService() {
            // 启动轮询服务
            // 暂时不考虑加入网络情况的判断...
            if (!LoopService.isServiceRuning) {
                // 用户是登录态,启动轮询服务
                if (UserConfig.isPassLogined()) {
                    // 判断当前长连接的状态,若长连接已连接,则不再开启轮询服务
                    if (MinaLongConnectManager.session != null && MinaLongConnectManager.session.isConnected()) {
                        LoopService.quitLoopService(context);
                        return;
                    }
                    LoopService.startLoopService(context);
                } else {
                    LoopService.quitLoopService(context);
                }
            }
        }
    
    }

    以上是通过NIO实现App长连接的部分核心代码,NIO中其实已经实现了长连接的核心流程,我们需要做的就是按照其流程实现长连接,需要注意的是要处理好异常情况,重连机制等。

    当长连接创建成功之后需要重新拉取一次服务器端的长连接消息,并且这里的长连接做了容错处理,当长连接断了之后需要有重连机制,一直启动轮训服务,当长连接修复之后轮训服务退出。以上只是通过MINA框架实现的长连接操作的核心流程,还有一些长连接实现的操作细节这里就不做过多的说明。

    总结:
    基本上对于App来说长连接已经是标配了,产品开发人员可以根据具体的产品需求选择不同的实现方式,一般而言使用第三方的推送服务已经可以满足大部分的需求了,当然了若是相对技术有所追求的话也可以选择自己实现一套长连接服务,不过其中可能存在一些坑需要填,希望这里的长连接实现能够对大家对长连接实现上有所帮助。

    • 可以通过使用第三方长连接服务或者是自己实现连接的方式;

    • 自定义实现长连接可以通过使用NIO或者是第三方NIO框架,比如MINA实现;

    • 长连接实现中通过心跳包的机制实现App与服务器的长时间连接;

    • 可以通过闹钟的机制定时检测长连接服务是否可靠,长连接是否出现异常等;

    • 为了消息的及时性,在长连接出现异常情况时可通过创建轮训服务的机制实现对消息的获取,待长连接恢复之后关闭轮训服务;


    另外对产品研发技术,技巧,实践方面感兴趣的同学可以参考我的:
    Android产品研发(一)–>实用开发规范
    Android产品研发(二)–>启动页优化
    Android产品研发(三)–>基类Activity
    Android产品研发(四)–>减小Apk大小
    Android产品研发(五)–>多渠道打包
    Android产品研发(六)–>Apk混淆
    Android产品研发(七)–>Apk热修复
    Android产品研发(八)–>App数据统计
    Android产品研发(九)–>App网络传输协议
    Android产品研发(十)–>不使用静态变量保存数据
    Android产品研发(十一)–>应用内跳转scheme协议


    本文以同步至github中:https://github.com/yipianfengye/AndroidProject,欢迎star和follow


    展开全文
  • React+socket.io 长连接实现聊天室

    千次阅读 2019-09-22 15:29:22
    使用socket.io长连接实现简单版聊天室 实现功能: 1、可以显示当前上线人数 2、发送消息 3、提示用户上线离线 使用技术 前端框架:react 后端:node express 所需的插件 服务器端 socket.io 将http服务和sockit服务...

    使用socket.io长连接实现简单版聊天室

    实现功能:
    1、可以显示当前上线人数
    2、发送消息
    3、提示用户上线离线

    使用技术

    前端框架:react
    后端:node express
    所需的插件 服务器端 socket.io
    将http服务和sockit服务合并 客户端 socket.io-client

    git地址

    https://github.com/Helen1314/chatRoom
    欢迎评论与转载(转载请声明出处)

    展开全文
  • Java Netty长连接实现Android推送

    千次阅读 2016-10-25 11:39:18
    Java Netty长连接实现Android推送,之前自己写socket来实现长连接发现有点不靠谱,正好同学公司在用netty推荐我使用这个,一试还真不错,现在分享出来给自己参考!

    罗嗦几句:

    1.轮询(Pull)客户端定时的去询问服务器是否有新消息需要下发;确点很明显Android后台不停的访问网络费电还浪费流量。
    
    2.推送(Push)服务端有新消息立即发送给客户端,这就没有时间的延迟,消息及时到达。
    
    当时需求过来之后就首先考虑的这两个,开发的角度Pull实现起来简单省事,但从用户来说省电和省流量才是主要的,所以最后选用Push。客户端与服务端使用长连接,客户端定时向服务端发送心跳包维持长连接,当时自己使用socket来写的,最后发现客户端数量大了之后不太好维护而且会莫名的掉线,最后无奈用了别人集成好的[Netty](http://netty.io/),用了之后才发现对并发处理和心跳的维护都处理的很好。
    

    正文开始

    先看最终的效果
    编辑好发送的内容,点击发送立即群发消息!
    

    这里写图片描述
    消息发送成功成功后会有消息提示,因为我只有一个手机连接,所以显示发送成功数1个,耗费的时间是2毫秒!
    这里写图片描述
    最后看手机的推送消息,看到效果还是挺不错的!
    这里写图片描述

    服务端设计:

    1. 数据库设计
      一.用户表,用户表的作用是在实际的项目运营中知道推送给指定的用户。主要有5个字段,第一个ID是主键,第二个use_id对应的是自己数据库中用户的编号,第三个ios_device_token方便给苹果用户推送,第四个mobile可以推送短信消息,第五个openid是用户授权登录系统后获取的用户标识,还有5个字段是保留字段方便扩展。

      CREATE TABLE `user` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
        `user_id` bigint(20) NOT NULL COMMENT '用户的ID',
        `ios_device_token` varchar(128) DEFAULT NULL COMMENT 'IOS的device_token令牌',
        `mobile` varchar(20) DEFAULT NULL COMMENT '用户手机号',
        `openid` varchar(200) DEFAULT NULL COMMENT '微信的OPENID',
        `obligate_1` varchar(100) DEFAULT NULL,
        `obligate_2` varchar(100) DEFAULT NULL,
        `obligate_3` varchar(100) DEFAULT NULL,
        `obligate_4` varchar(100) DEFAULT NULL,
        `obligate_5` varchar(100) DEFAULT NULL,
        PRIMARY KEY (`id`),
        KEY `user_id` (`user_id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=1028 DEFAULT CHARSET=utf8;

      二.消息表,系统在运营中会产生消息记录,消息记录的记载,记载之后可以保存用户离线消息,等待上线后Push给对应的用户,并且有消息有效期,超过有消息自动作废。同样的有4个保留字段方便扩展使用。

      CREATE TABLE `msg_que` (
        `id` bigint(20) NOT NULL AUTO_INCREMENT,
        `user_id` bigint(20) NOT NULL COMMENT '用户ID',
        `msg_title` varchar(20) DEFAULT NULL COMMENT '消息头',
        `msg_type` smallint(1) DEFAULT '0' COMMENT '消息类型(0:普通消息;1:透传消息)',
        `msg_content` text COMMENT '消息内容',
        `create_time` timestamp NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '消息创建时间',
        `valid_time` datetime DEFAULT '0000-00-00 00:00:00' COMMENT '消息有效时间(小时)',
        `state` smallint(1) DEFAULT '0' COMMENT '消息状态(0:未送达;1:已送达;2:失效)',
        `obligate_1` varchar(100) DEFAULT NULL,
        `obligate_2` varchar(100) DEFAULT NULL,
        `obligate_3` varchar(100) DEFAULT NULL,
        `obligate_4` varchar(100) DEFAULT NULL,
        PRIMARY KEY (`id`),
        KEY `user_id_refrence` (`user_id`),
        CONSTRAINT `user_id_refrence` FOREIGN KEY (`user_id`) REFERENCES `user` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
      ) ENGINE=InnoDB AUTO_INCREMENT=4446 DEFAULT CHARSET=utf8;
      
    2. 代码设计
      推送服务端使用的是Spring+SpringMVC+MyBatis,下面两个类就是服务端的启动代码与消息处理代码,一张图解释设计流程(凑合看吧):
      这里写图片描述

        /**
        *服务端启动Netty
        */
        @Controller
        public class NettyInitial implements InitializingBean, ServletContextAware {
    
            static final boolean SSL = System.getProperty("ssl") != null;
            //服务端netty的端口
            static final int NETTYPORT = Integer.parseInt(System.getProperty("port", "9800"));
    
            @Override
            public void setServletContext(ServletContext arg0) {
                //使用线程启动Netty服务端,否则会阻塞线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            startNettyConnect();
                        }catch (Exception e) {
                        }
                    }
                }).start();
            }
            @Override
            public void afterPropertiesSet() throws Exception {
            }
    
            public void startNettyConnect() throws CertificateException, SSLException{
                final SslContext sslCtx;
                if (SSL) {
                    SelfSignedCertificate ssc = new SelfSignedCertificate();
                    sslCtx = SslContext.newServerContext(ssc.certificate(),
                            ssc.privateKey());
                } else {
                    sslCtx = null;
                }
    
                // Configure the server.
                EventLoopGroup bossGroup = new NioEventLoopGroup(10);
                EventLoopGroup workerGroup = new NioEventLoopGroup(10);
                try {
                    ServerBootstrap b = new ServerBootstrap();
                    b.group(bossGroup, workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .option(ChannelOption.SO_BACKLOG, 2048)
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel ch)
                                        throws Exception {
                                    ChannelPipeline p = ch.pipeline();
                                    if (sslCtx != null) {
                                        p.addLast(sslCtx.newHandler(ch.alloc()));
                                    }
                                    p.addLast("idle", new IdleStateHandler(300, 300, 300));
                                    p.addLast(new ObjectEncoder(), new ObjectDecoder(
                                            ClassResolvers.cacheDisabled(null)),
                                            new NettyServerHandler());
                                }
                            });
                    b.option(ChannelOption.TCP_NODELAY, true);
                     //保持长连接状态
                    b.childOption(ChannelOption.SO_KEEPALIVE, true);
                    // Start the server.
                    ChannelFuture f = b.bind(NETTYPORT).sync();
                    // Wait until the server socket is closed.
                    f.channel().closeFuture().sync();
                }catch (Exception e) {
                    // TODO: handle exception
                } finally {
                    // Shut down all event loops to terminate all threads.
                    bossGroup.shutdownGracefully();
                    workerGroup.shutdownGracefully();
                }
            }
        }
    
    
        /**
         * Netty模块消息处理
         * @author Administrator
         *
         */
        public class NettyServerHandler extends ChannelInboundHandlerAdapter{
    
            private MainHashMapObserver mainHashMapObserver = MainHashMapObserver.getInstace();
    
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) {
                mainHashMapObserver.addObserver(msg.toString(), (SocketChannel) ctx.channel());
                ctx.writeAndFlush("连接成功="+msg);
            }
    
            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                super.channelActive(ctx);
            }
    
            @Override
            public void channelReadComplete(ChannelHandlerContext ctx) {
                ctx.flush();
            }
    
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                mainHashMapObserver.deleteObserver((SocketChannel) ctx.channel());
                ctx.close();
            }
    
            @Override
            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                super.userEventTriggered(ctx, evt);
                if (evt instanceof IdleStateEvent) {
                    IdleStateEvent event = (IdleStateEvent) evt;
                    if (event.state().equals(IdleState.READER_IDLE)) {
                    // 读超时
    
                    }
                    if(event.state().equals(IdleState.WRITER_IDLE)){
                    //写超时
                        ctx.writeAndFlush("y");
                    }
                }
            }
        }
    

    客户端设计

    1. service代码

      将Android服务定义成远程服务,可以稍微避免一下下被系统杀死;在接收到服务器推送的消息后判断消息类型,如果是心跳包就不必理会,如果是有用的消息就选择notification或者后台处理

        public class NettyRemoteService extends Service {
    
        /**
         * 单一线程池
         */
        private ExecutorService executorService= Executors.newSingleThreadExecutor();
    
        /**
         * 是否需要继续运行
         */
        private boolean running = true;
    
        @Override
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            try {
                create();
            } catch (SSLException e) {
            }
    
            executorService.execute(new Mythread());
    
            return START_STICKY;
        }
    
    
        @Override
        public void onDestroy() {
            running = false;
            executorService.shutdown();
            if(f != null){
                f.channel().close();
            }
            stopSelf();
            super.onDestroy();
        }
    
    /***********************************************************************************
         * start Netty长连接服务器保持数据通讯
         ***********************************************************************************/
        private static final boolean SSL = System.getProperty("ssl") != null;
        SslContext sslCtx;
    
        private void create() throws SSLException {
            if (SSL) {
                sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE);
            } else {
                sslCtx = null;
            }
        }
        ChannelFuture f;
        public void createBootStrap() {
    
            L.v("重连......");
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline p = ch.pipeline();
                                if (sslCtx != null) {
                                    p.addLast(sslCtx.newHandler(ch.alloc(), "192.168.0.103", 9800));
                                }
                                p.addLast("idle", new IdleStateHandler(300, 300, 300));
                                p.addLast(new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)),
                                        new CarmgrClientHandler());
                            }
                        });
    
                //设置TCP协议的属性
                bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
                bootstrap.option(ChannelOption.TCP_NODELAY, true);
                bootstrap.option(ChannelOption.SO_TIMEOUT, 5000);
                // Start the client. IP和Port是服务器运行的IP和自己设置的端口
                f = bootstrap.connect("192.168.0.103", 9800).sync();
                f.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture channelFuture) throws Exception {
                        if (channelFuture.isSuccess()) {
    
                        } else {
    
                        }
                    }
                });
                f.channel().closeFuture().sync();
            } catch (Exception e) {
            } finally {
                // Shut down the event loop to terminate all threads.
                group.shutdownGracefully();
            }
    
        }
    
        private class Mythread extends Thread{
            @Override
            public void run() {
                while(running) {
                    createBootStrap();
                    try {
                        //休息30s重连
                        Thread.sleep(30 * 1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
    
        /**
         * 开启推送消息展示
         */
        private int notificaionId = 100;
    
        public void startNotification(String title, String context) {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    
            // 设置通知的基本信息:icon、标题、内容
            builder.setSmallIcon(R.mipmap.launcher_icon);
            builder.setContentTitle(title);
            builder.setContentText(context);
            builder.setAutoCancel(true);
    
            // 设置通知的优先级
            builder.setPriority(NotificationCompat.PRIORITY_MAX);
            Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            // 设置通知的提示音
            builder.setSound(alarmSound);
            builder.setDefaults(Notification.DEFAULT_ALL);
    
            // 设置通知的点击行为:这里启动一个 Activity
            Intent intent = new Intent(this, MainActivity.class);
            PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            builder.setContentIntent(pendingIntent);
    
            if (notificaionId < 1000) {
                notificaionId++;
            } else {
                notificaionId = 100;
            }
    
            // 发送通知 id 需要在应用内唯一
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(notificaionId, builder.build());
    
        }
    
        /**
         * Netty消息接收内部类
         */
        private class CarmgrClientHandler extends ChannelInboundHandlerAdapter {
    
            /**
             * Creates a client-side handler.
             */
            public CarmgrClientHandler() {
                //TODO
            }
    
            @Override
            public void channelActive(ChannelHandlerContext ctx) {
                ctx.writeAndFlush("1");
            }
    
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) {
    
                if (msg.toString().equals("y")) {
                    //心跳不用理会
                } else {
                    //处理服务端推送的消息
                }
            }
    
            @Override
            public void channelReadComplete(ChannelHandlerContext ctx) {
                ctx.flush();
            }
    
    
            @Override
            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                // Close the connection when an exception is raised.
                ctx.close();
            }
    
            @Override
            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                super.channelInactive(ctx);
            }
    
    
            @Override
            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                super.userEventTriggered(ctx, evt);
    
                if (evt instanceof IdleStateEvent) {
                    IdleStateEvent e = (IdleStateEvent) evt;
                    switch (e.state()) {
                        case WRITER_IDLE:
                            ctx.writeAndFlush("1");
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    
        /***********************************************************************************
         * end Netty长连接服务器保持数据通讯
         ***********************************************************************************/
    }

    在完成这个推送的过程中也遇到过很多的问题,比如说Android service在后台运行一段时间就会挂掉然后掉线,这个问题的解决主要是提高service的存活率来着手,比如说设置成remote service、监听一些广播事件,两个服务相互监听互相吊起等,但是注意最后别搞成关不了的流氓软件了。

    还有就是连接的用户数过多的时候群发消息问题,我自己的机器模拟启动1000个线程去连接服务端,写一个接口去获取当前的连接数:
    这里写图片描述

    运行2个小时后再次查看接口,发现还是833个,也就说一个都没有掉线(电脑端的网络比较稳定,手机端很复杂,所以手机端会出现掉线然后迅速重连的现象!);这时候再次测试群发数据查看到达率和耗时
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述

    消息推送所耗费的时间跟所发送数据大小(下面是发送少量数据所耗时)、心跳的时间间隔、电脑的带宽、电脑的性能都有影响
    这里写图片描述
    发送的消息我封装成JOSN字符串,所以实际的发送数据量比看见的还会稍微大点,全部送达而且耗费的时间还可以接受。正常情况下以800个120毫秒计算,每个0.15毫秒算在用户量不太大的情况下还可以接受。
    这里写图片描述
    数据量极少的情况下出现了耗时6ms的情况,也就是1s不到可以推送10万,当然这是理想的情况,服务端连接数达到10万还不知道会发生什么情况;单机测试就是这么的有局限性,如果多配几个电脑就能测试的更加准确。

    至此这个推送就完成了,有很多代码或者说细节的问题没有在这里展示,因为是公司的项目所以选择性的粘贴了部分主流代码和思路,按照思路来的话是可以实现的,如果有不懂的欢迎留言咨询,互相进步!

    展开全文
  • 本人最近做一个需求,需要涉及同一个浏览器多个页签共享一个Websocket长连接实现方案,有相关经验大神给予指点?
  • 基于 java nio 长连接实现的聊天室

    千次阅读 2016-01-26 14:51:04
    基于 java nio 长连接实现的聊天室
  • Socket长连接实现思路

    千次阅读 2017-04-19 10:11:17
    长连接的正确实现方式 1、不关闭流实现长连接? 流关闭了而不关闭Socket,还是无法达到长连接的效果的,所以,要长连接,流必须不能关闭!那么,是不是直接不关闭流,然后每次要发消息就直接往流里面任进去数据,...
  • Ajax 长连接实现

    万次阅读 2016-03-27 23:42:16
    然后,一个新的轮询连接立即重新打开,等待新的事件到达客户端。 Code Demo Client: Server: 一般来说,Comet with Ajax long-pollin是一种好的折衷方式来满足: 低延迟通信超时和错误...
  • 服务器进程间RPC长连接实现的思考

    万次阅读 2015-12-19 00:34:33
    服务器进程间RPC长连接实现的思考  最开始比较系统的接触rpc应该还是在支付宝实习的时候,当时给的任务就是利用netty写一个java的rpc框架,然后后期还有了接入config的需求,这应该算的上是自己比较全面的接触到...
  • TCP-long-connection-based-on-Apache-mina基于Apache mina 的tcp长连接实现,可用于android客户端推送。项目将Apache的mina项目移植到了android平台。实现长连接的主要思想是使用了mina的KeepAliveFilter过滤器。...
  • 服务器端推送消息-客户端接收消息,利用websocket实现长连接无刷新消息推送
  • ajax长连接实现web实时通讯

    万次阅读 2014-02-21 16:20:53
    本文介绍在web上真正实现实时通讯的方法,ajax长连接。 特别说明:ajax长连接是浏览器兼容的  简述过程:进入页面客户端即发起一个ajax请求,服务端收到请求后不立即返回,将其阻塞,待服务端有新信息时将信息返回...
  • Java Socket长连接实现

    千次阅读 2013-07-10 15:38:16
    TestScoket.java 单例模式实现,继承Socket 1 public class TestSocket 2 extends Socket { 3 4 private static TestSocket instance; 5 6 private TestSocket() { 7 } 8
  • 基于iframe的HTTP长连接实现

    千次阅读 2010-06-19 13:22:00
    关于什么是http长连接我不废吐沫了,有专业的解释(http://www.ibm.com/developerworks/cn/web/wa-lo-comet/)你可以去看看我们介绍一下在struts下的实现首先写一个test.jsp(写一些片段)view plaincopy to clipboard...
  • 1)建立在 TCP 协议之上,服务器端的实现比较容易。 (2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。 (3)数据格式...
  • 最近听说有“HTTP长连接”,去探索了一把,果然很有意思,能够实现“服务器推”的这种概念,也就是服务器是主动发送请求,客户端是被动接受请求。 关于“服务器推”及“HTTP长连接”的概念网上很多,给一个比较...
  • 实现服务端主动推送消息,最简单地是利用
  • 那么我们要想项目具备长连接的功能现在又两种选择的方案,一种基于原生tcp协议的socket长连接,另外一种基于ws协议的websocket的长连接,今天我们演示两种socket长连接实现集成方式(1、基于Oksocet框架实现socket...
  • Java实现Socket长连接和短连接

    千次下载 热门讨论 2014-09-21 11:32:43
    Java实现Socket长连接和短连接,实现原理可参见个人博客
  • Websocket如何实现长连接

    千次阅读 2019-12-02 11:30:25
    实现长连接 由于websocket连接成功过段时间自动关闭,无法继续获取消息 于是我就想到一个办法,就是自动关闭时再重新创建一次(因为自动关闭也不是很快就关闭的,所以我就这么写),虽然实现方式不是很好,但是也...
  • java实现长连接

    万次阅读 2018-10-22 15:33:32
    java实现长连接 package socket.keepalive.test; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; ...
  • 页面实现长连接

    千次阅读 2018-01-29 22:59:10
    基于HTTP的长连接,是一种通过长轮询方式实现”服务器推”的技术,它弥补了HTTP简单的请求应答模式的不足,极大地增强了程序的实时性和交互性。 长连接、长轮询:都是通过不断的向服务器发出请求,如果服务器有数据则...
  • 使用MINA实现长连接

    2016-08-13 17:16:41
    使用MINA实现长连接
  • php 实现长连接

    万次阅读 2016-06-14 21:28:47
    长连接技术(Long Polling) 在服务器端hold住一个连接, 不立即返回, 直到有数据才返回, 这就是长连接技术的原理 长连接技术的关键在于hold住一个HTTP请求, 直到有新数据时才响应请求, 然后客户端再次自动发起长连接...
  • iOS使用socketIO实现长连接

    万次阅读 2017-06-08 19:57:17
    iOS使用socketIO实现长连接

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,101,509
精华内容 440,603
关键字:

长连接实现