安卓开发实现长连接,心跳,java后端,实现消息推送,持续更新中

xiaoxiao2021-02-28  79

个人理解 

//实现通知的方法 public void simpleNotice(String msg) { Log.e("弹窗",":执行啦"); Builder mBuilder = new Builder(this); mBuilder.setSmallIcon(R.mipmap.ic_launcher); mBuilder.setContentTitle("通知标题3"); mBuilder.setContentText(msg); //设置点击一次后消失(如果没有点击事件,则该方法无效。) mBuilder.setAutoCancel(true); //点击通知之后需要跳转的页面 Intent resultIntent = new Intent(this, Activity_Two.class); //使用TaskStackBuilder为“通知页面”设置返回关系 TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); //为点击通知后打开的页面设定 返回 页面。(在manifest中指定) stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(resultIntent); PendingIntent pIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(pIntent); nm.notify(2, mBuilder.build()); } //连接socket 并得到服务器发过来的数据 void getConnectNotiFaction(){ new Thread(new Runnable() { @Override public void run() { BufferedReader reader; try { Socket socket = new Socket("192.168.43.220", 9898); //下面是服务端发来的数据 reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); while((receivedMsg=reader.readLine())!=null){ Log.e("客户端收到的","receivedMsg:"+receivedMsg); simpleNotice(receivedMsg); } } catch (IOException e) { e.printStackTrace(); } finally { } } }).start(); } java后台代码

public class SocketServlet { public static void main(String[] args) { SocketServlet s=new SocketServlet(); s.start(); } void start(){ BufferedReader reader; BufferedWriter write;    String mess ="服务端发送的数据"; try { ServerSocket serverSocket=new ServerSocket(9898); System.out.println("server started..."); //会停在这  一直等消息 Socket socket= serverSocket.accept(); System.out.println("client connect..."); //服务端往客户端发数据 InputStream inputStream = new ByteArrayInputStream(mess.getBytes());          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);          BufferedReader  inputReader = new BufferedReader(inputStreamReader);          BufferedWriter writer;          String inputContent="服务器的值";         writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));      //   while (!(inputContent = inputReader.readLine()).equals("bye")) {             writer.write(inputContent + "\n");             writer.flush();             System.out.println("数据库发送的值:"+inputContent);        // } //下面是客户端发来的数据   // reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); // String receivedMsg; // while((receivedMsg=reader.readLine())!=null){ // System.out.println(receivedMsg); // } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }

//-------------------java结束

解释一下,上面安卓代码直接可以复制到当前类里面用,  就是一个连接后台socket的方法和一个弹通知的方法 直接用就行

//-----------------------------华丽分割线-----------------------------------

在写之前,我们首先了解一下,为什么Android维护长连接需要心跳机制,首先我们知道,维护任何一个长连接都需要心跳机制,客户端发送一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的。如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么,对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可。那么,在智能手机上的长连接心跳和在Internet上的长连接心跳有什么不同的目的呢?原因就在于智能手机使用的是移动无线网络,那么,我们在讲长连接之前,首先要了解无线移动网络的特点。

1.无线移动网络的特点:

        当一台智能手机连上移动网络时,其实并没有真正连接上Internet,运营商分配给手机的IP其实是运营商的内网IP,手机终端要连接上Internet还必须通过运营商的网关进行IP地址的转换,这个网关简称为NAT(NetWork Address Translation),简单来说就是,手机终端连接Internet,其实就是移动内网IP,端口,外网IP之间相互映射。相当于在手机终端在移动无线网络这堵墙上,打个洞与外面的Internet相连。原理图如下:(来源网络)

        GGSN(GateWay GPRS Support Note 网关GPRS支持节点)模块就实现了NAT功能,由于大部分的移动无线网络运营商,为了减少网关NAT映射表的负荷,如果一个链路有一段时间没有通信时就会删除其对应表,造成链路中断,正是这种刻意缩短空闲连接的释放超时,原本是想节省信道资源的作用,没想到让互联网的应用不得不以远高于正常频率,发送心跳来维护推送的长连接。这也是为什么会有之前的信令风暴,微信要收费的传言,因为这类的应用发送心跳的频率是很短的,既造成了信道资源的浪费,也造成了手机电量的快速消耗。

2.android系统的推送和iOS的推送有什么区别:

        首先我们必须知道,所有的推送功能必须有一个客户端和服务器的长连接,因为推送是由服务器主动向客户端发送消息,如果客户端和服务器之间不存在一个长连接,那么服务器是无法来主动连接客户端的。因而推送功能都是基于长连接的基础上的。

        ios长连接是由系统来维护的,也就是说,苹果的IOS系统,在系统级别维护了一个客户端和苹果服务器的长链接,IOS上的所有应用上的推送,都是先将消息推送到苹果的服务器,然后,苹果服务器通过这个系统级别的长链接,推送到手机终端上。这样做的几个好处为:1、在手机终端始终只要维护一个长连接即可,而且由于这个长链接是系统级别的,因此,不会出现被杀死而无法推送的情况。2、省电,不会出现每个应用都各自维护一个自己的长连接。3、安全,只有在苹果注册的开发者才能够进行推送,等等。

       Android的长连接是由每个应用各自维护的,但是google也推出了和苹果技术架构相似的推送框架,C2DM,云端推送功能,但是由于google的服务器不在中国境内,其他的原因,你懂的,所以导致这个推送无法使用,android的开发者不得不自己去维护一个长链接,于是每个应用如果都24小时在线,那么都得各自维护一个长连接,这种电量和流量的消耗是可想而知的。虽然国内也出现了各种推送平台,但是都无法达到只维护一个长连接这种消耗的级别。

3.推送的实现方式:

一、客户端不断的查询服务器,检索新内容,也就是所谓的pull 或者轮询方式。

二、客户端和服务器之间维持一个TCP/IP长连接,服务器向客户端push。

三、服务器有新内容时,发送一条类似短信的信令给客户端,客户端收到后从服务器中下载新内容,也就是SMS的推送方式。

苹果的推送系统和google C2DM推送系统,其实都是在系统级别维护一个TCP/IP长连接,都是基于第二种的方式进行推送的。第三种方式,由于运营商没有免费开放这种信令,导致了这种推送在成本上是无法接受的,虽然这种推送的方式非常的稳定,高效和及时。

如果想了解android中各种推送方式,请参考这个链接:Android实现推送方式解决方案 这篇博客已经介绍的非常好了。

所谓的心跳包就是客户端定时发送简单的信息给服务器端,告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务器端,服务器端回复一个固定信息。如果服务器端几分钟后没有收到客户端信息,则视为客户端断开。比如有些通信软件长时间不使用,要想知道它的状态是在线还是离线,就需要心跳包,定时发包收包。

    心跳包之所以叫心跳包,是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。

     在TCP机制里面,本身是存在有心跳包机制的,也就是TCP选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。

Socket长连接+心跳检测:http://blog.csdn.NET/zh724738989/article/details/42007099

心跳包的机制,其实就是传统的长连接。或许有的人知道消息推送的机制,消息推送也是一种长连接 ,是将数据由服务器端推送到客户端这边,从而改变传统的“拉”的请求方式。下面我来介绍一下安卓和客户端两个数据请求的方式

       1、push  这个也就是由服务器推送到客户端这边,现在有第三方技术,比如极光推送。

       2、pull   这种方式就是客户端向服务器发送请求数据(http请求)

一、首先服务器和客户端有一次“握手”

[javascript]  view plain  copy public void connect()         {           LogUtil.e(TAG, "准备链接...");           InetAddress serverAddr;           try {               socket = new Socket(Config.Host, Config.SockectPort);               _connect = true;               mReceiveThread = new ReceiveThread();               receiveStop = false;               mReceiveThread.start();               LogUtil.e(TAG, "链接成功.");              } catch (Exception e) {               LogUtil.e(TAG, "链接出错." + e.getMessage().toString());               e.printStackTrace();           }       }   二、下面就要开启一个线程  去不断读取服务器那边传过来的数据,采用Thread去实现

[javascript]  view plain  copy private class ReceiveThread extends Thread {           private byte[] buf;           private String str = null;              @Override           public void run() {               while (true) {                   try {                       // LogUtil.e(TAG, "监听中...:"+socket.isConnected());                       if (socket!=null && socket.isConnected()) {                              if (!socket.isInputShutdown()) {                               BufferedReader inStream = new BufferedReader(                                       new InputStreamReader(                                               socket.getInputStream()));                               String content = inStream.readLine();                                                           if (content == null)                                   continue;                               LogUtil.e(TAG, "收到信息:" + content);                               LogUtil.e(TAG, "信息长度:"+content.length());                               if (!content.startsWith("CMD:"))                                   continue;                               int spacePos = content.indexOf(" ");                               if (spacePos == -1)                                   continue;                               String cmd = content.substring(4, spacePos);   //                            String body = StringUtil.DecodeBase64(content   //                                    .substring(spacePos));                               String body = content.substring(spacePos).trim();                               LogUtil.e(TAG, "收到信息(CMD):" + cmd);                               LogUtil.e(TAG, "收到信息(BODY):" + body);                               if (cmd.equals("LOGIN"))                              {                                   // 登录                                   ReceiveLogin(body);                                   continue;                               }                                 if (cmd.equals("KEEPLIVE")) {                                   if (!body.equals("1")) {                                       Log.e(TAG, "心跳时检测到异常,重新登录!");                                       socket = null;                                       KeepAlive();                                   } else {                                       Date now = Calendar.getInstance().getTime();                                       lastKeepAliveOkTime = now;                                   }                                   continue;                               }                           }                       } else {                           if(socket!=null)                               LogUtil.e(TAG, "链接状态:" + socket.isConnected());                       }                      } catch (Exception e) {                       LogUtil.e(TAG, "监听出错:" + e.toString());                       e.printStackTrace();                   }               }           }   三 、 Socket 是否断开了  断开了 需要重新去连接

[javascript]  view plain  copy public void KeepAlive()           {           // 判断socket是否已断开,断开就重连           if (lastKeepAliveOkTime != null) {               LogUtil.e(                       TAG,                       "上次心跳成功时间:"                               + DateTimeUtil.dateFormat(lastKeepAliveOkTime,                                       "yyyy-MM-dd HH:mm:ss"));               Date now = Calendar.getInstance().getTime();               long between = (now.getTime() - lastKeepAliveOkTime.getTime());// 得到两者的毫秒数               if (between > 60 * 1000) {                   LogUtil.e(TAG, "心跳异常超过1分钟,重新连接:");                   lastKeepAliveOkTime = null;                   socket = null;               }              } else {               lastKeepAliveOkTime = Calendar.getInstance().getTime();           }              if (!checkIsAlive()) {               LogUtil.e(TAG, "链接已断开,重新连接.");               connect();               if (loginPara != null)                   Login(loginPara);           }          //此方法是检测是否连接         boolean checkIsAlive() {           if (socket == null)               return false;           try {               socket.sendUrgentData(0xFF);           } catch (IOException e) {               return false;           }           return true;          }      //然后发送数据的方法       public void sendmessage(String msg) {           if (!checkIsAlive())               return;           LogUtil.e(TAG, "准备发送消息:" + msg);           try {               if (socket != null && socket.isConnected()) {                   if (!socket.isOutputShutdown()) {                       PrintWriter outStream = new PrintWriter(new BufferedWriter(                               new OutputStreamWriter(socket.getOutputStream())),                               true);                          outStream.print(msg + (char) 13 + (char) 10);                       outStream.flush();                   }               }               LogUtil.e(TAG, "发送成功!");           } catch (Exception e) {               e.printStackTrace();           }       }  

最近做项目用到心跳轮询到主动到服务器取消息,为了做推送。坑了个爹的,极光百度推送限制多不能满足需求,只能自己写…手机主动到Service取数据,也就意味着你的手机要有一个服务,一直在后台运行,在特定的时间去服务器询问有没有消息,如果有消息则取回客户端。  当然还可以用像什么XMPP(当然为了一个消息推送,动用那么大而又笨重的东西,很明显不明智),短信通知等等一下方式。

这里主要讲在android主动取数据:  其实实现后台推送消息给客户端可以分为主动取,和主动推两种。

主动取:就是我们上面说的轮询服务器取消息。  主动推:服务器推送消息给客户端,这里必须客户端和服务器保持长连接。  两种形式各有利弊,“主动取”不能保证消息的实时性;“主动推”能保证消息的实时性,但是不能保证android端的这个链接不会被kill掉。

实现轮询

原理  其原理在于在android端的程序中,让一个SERVICE一直跑在后台,在规定时间之内调用服务器接口进行数据获取。

这里的原理很简单,当然实现起来也不难;

然后,这个类之中肯定要做网络了数据请求,所以我们在Service中建立一个线程(因为在android系统中网络请求属于长时间操作,不能放主线程,不然会导致异常),在线程中和服务器进行通信。

最后,这个逻辑写完后,我们需要考虑一个问题,如何进行在规定时间内调用该服务器,当然可以用Thread+Handler(这个不是那么稳定),也可以使用AlarmManager+Thread(比较稳定),因为我们需要其在后台一直运行,所以可以依靠系统的AlarmManager这个类来实现,AlarmManager是属于系统的一个闹钟提醒类,通过它我们能实现在规定间隔时间调用,并且也比较稳定,这个service被杀后会自己自动启动服务。

这个博客写的不错 ,可以借鉴一下 http://blog.csdn.net/q376420785/article/details/8653958 如何实现android和服务器长连接呢?推送消息的原理
转载请注明原文地址: https://www.6miu.com/read-49656.html

最新回复(0)