基于Socket长连接的推送实现

支持心跳包检测

Posted by MrCodesniper on March 6, 2018

原理

Socket连接是封装与TCP协议的一种连接形式,可以在C/S两端建立起双向传输管道pipe使得可以进行流传输,是一种长连接机制

推送作为一个具有一定市场的APP是非常需要的,实现推送也有非常多的形式 这里我们讨论使用socket来实现App推送,从上面的socket介绍来看

他符合作为推送所需要的几个要素:实时,信息交互,轻量快捷

本实例主要是通过在JAVAEE后端建立服务器socket与app上的包含客户socket的service进程通过socket交互来实现推送

核心功能:

1.服务端推送的信息 能唤起app的通知栏信息 并能跳转到相应的url

2.客户端每隔一定时间发送心跳包 若服务器没有响应则重新建立socket连接

客户端

推送核心实现

   新建自定义Service并在配置清单中单开进程

   下面这段为核心实现代码

     so = new Socket(HOST, PORT);
     mReadThread = new ClientThread();
     mReadThread.start();
     
     class ClientThread extends Thread{

        //该线程所处理的Socket对应的输入流
        BufferedReader br = null;
        private boolean isStart = true;

        @Override
        public void run() {
            if (null != so) {
                try {
                    String content = null;
                    br = new BufferedReader(new InputStreamReader(so.getInputStream()));//得到输入流
                    while (!so.isClosed() && !so.isInputShutdown()
                            && isStart && (content = br.readLine()) != null) {
                        Message msg = new Message();
                        msg.obj =content;//将输入流转换为字符串
                        //收到服务器过来的消息,就通过Broadcast发送出去
                        if(content.equals("HeartBeat——返回自服务器")){//处理心跳回复
                            msg.what = 0x666;
                            handler.sendMessage(msg);
                        }else{//处理消息
                            msg.what = 0x123;
                            handler.sendMessage(msg);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    Handler handler= new Handler() {
        public void handleMessage(android.os.Message msg) {
            if (msg.what == 0x123) {
                NotifyUtils notifyUtils=new NotifyUtils(getApplicationContext(),msg.obj.toString());
                notifyUtils.show();//显示通知
            }else if(msg.what == 0x666){
                Logger.e("收到心跳表示socket还在不需要初始化");
            }
        };
    };
    

  从上述代码可以清晰的看到在service的子线程中获取socket连接的输入流获取服务端数据   将推送服务 心跳包服务 区分执行 最后使用通知工具类显示推送消息

心跳核心实现

/**心跳频率*/
private static final long HEART_BEAT_RATE = 10 * 1000;//10s
private void initSocket() {//初始化Socket
        try {
            so = new Socket(HOST, PORT);
            mReadThread = new ClientThread();
            mReadThread.start();
            handler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);//初始化成功后,就准备发送心跳包
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

可以看到在初始化socket连接后 hanlder发送了延迟10s的Runnable

我们来看看这个runnable进行了哪些操作

 /**心跳任务,不断重复调用自己*/
    private Runnable heartBeatRunnable = new Runnable() {
        @Override
        public void run() {
            if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {
                sendMsg("HeartBeat");//就发送一个\r\n过去 如果发送失败,就重新初始化一个socket
            }
            handler.postDelayed(this, HEART_BEAT_RATE);
        }
    };
    
public void sendMsg(final String msg) {//向服务端写入HeartBeat,若服务器没有回复就是断开不成功
        if (null == so ) {
            return;
        }
        if (!so.isClosed() && !so.isOutputShutdown()) {
            new AsyncTask<Void, Void, Boolean>() {
                @Override
                protected Boolean doInBackground(Void... voids) {
                    try {
                        OutputStream os = so.getOutputStream();
                        String message = msg + "\r\n";
                        os.write(message.getBytes());
                        os.flush();
                    } catch (IOException e) {
                        e.printStackTrace();
                        return false;
                    }
                    return true;
                }

                @Override
                protected void onPostExecute(Boolean aBoolean) {
                    super.onPostExecute(aBoolean);
                    sendTime = System.currentTimeMillis();//每次发送成数据,就改一下最后成功发送的时间,节省心跳间隔时间
                    if (!aBoolean) {
                        Logger.e("初始化socket");
                        handler.removeCallbacks(heartBeatRunnable);
                        mReadThread.release();
                        releaseLastSocket(so);
                        new InitSocketThread().start();//如果发送失败,就重新初始化一个socket连接
                    }
                }
            }.execute();
        } else {
            return;
        }
    }    

在Runnable中每隔10s周期会创建一个asynctask调用线程使用输出流往服务端写心跳包内容 若写入出错 表示socket连接失败 此时就需要重新初始化一个socket连接 防止连接中断

进程保活的一些措施

1.本实例中提高了进程优先级来实现保护socket连接的目的

 @Override
    public void onCreate() {
        //提高进程优先级
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(250, builder.build());
            startService(new Intent(this, CancelService.class));
        } else {
            startForeground(250, new Notification());
        }
    }

在service的oncrate中将进程转为优先级最高的前台进程

在具体的app效果中将本app进程结束 连接还在保持并持续发送心跳包

且app能正常接受服务端的推送

但是进程的保活还有许多方法等待着我们去实现

2.其他保活方法

服务端

参考: