目前,大多数的手机软件都具有推送功能,比如手机会每天定时给你推送相应的新闻,天气,和你订阅的一些信息,聊天软件信息也是实时推送,可以说推送功能是一个网络APP必备的功能。很多Android初学者(本文希望能给刚刚接触Android开发的人也能看懂,有些地方写得比较啰嗦请老鸟们忽略)希望能够掌握一个Android推送的原理和方法,但迄今为止,苦于没有一篇详细介绍此类的文章。而且现在很多第三方推送平台使用起来很方便(比如极光推送),但是醉心技术的你肯定希望掌握其中的实现原理和方法吧,因此我将会写作几篇相应的博文来和大家共同探讨Android推送服务器到客户端的具体实现方案。包括在线推送,离线消息接收,文字推送,别名推送,标签推送,富文本推送等功能。
(源码)github:https://github.com/xuyusong/socket-long-connect
准备在阅读本文前,假设读者已经具有了一定Java开发基础,Android开发基础,一些spring的基本知识(这方面要求不高,主要在服务器上少量用到)。
本文中需要大家准备几样基本的工具和环境:Java环境配置,eclipse+Android SDK,Myeclipse,Mysql数据库,之后还会用到Mina,Androidpn等框架(具体我会教大家下载方法)。
推送原理推送,顾名思义便是从服务器将消息推送到客户端上,这种方式和普通我们上网获取信息有什么区别呢:
举个例子:我们浏览某个网页必须要点击某个链接或者按钮才能有对应的内容发送到浏览器,就是我们必须给后台服务器发送一个请求(我们需要XXX数据),然后后台根据我们的请求,给我们发送相应的内容。这就是HTTP协议的Request/Response请求响应模型,是一种无状态的协议,客户端和服务器端不需要建立持久的链接。客户端和服务器的链接是基于一种请求应答模式。及客户端和服务器建立一个链接,客户端提交一个请求,服务器端收到请求后返回一个响应,然后二者就断开链接。
推送则必须要服务器和客户端有持久的联系,就像QQ,微信一样必须时刻能够接收数据,而不是我们想看消息时再从服务器去获取,那么如何实现以上功能,目前为止,有三种方式:
轮询(pull)方式,通过客户端不断向服务器询问是否有消息。缺点是十分耗电,而且会消耗大量网络资源。 ** S(Push)方式,通过拦截 ** S消息(即短信)来解析服务器信息。缺点是必须要给运营商支付高额费用。持久连接(Push)方式,通过服务器与客户端建立持久连接来接受实时的服务器消息。目前最可行的方案,我们也将采用这种方式来实现。针对第三种方式也有几个不同的实现方案,较为成熟的有一下几种:
第一种:C2DM云端推送,在Android手机上,Google提供了C2DM(Cloudto Device Messaging)服务,一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。但是由于国内的网络环境,是没法使用该服务器的,因此所有国内软件都没有采用这种方式。
C2DM推送
第二种:使用国内推送平台,如极光、百度、腾讯等推送都做的很成熟了,而且只需要简单的集成推送平台的SDK就快速的实现推送功能,而且有些服务也是免费的。具体集成方法我就不多说了,官网上都有详细的集成指南。
第三种:采用MQTT协议实现Android推送,MQTT是IBM开发的一个即时通讯协议,该协议支持所有平台。不仅如此使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;对负载内容屏蔽的消息传输;使用 TCP/IP 提供网络连接;有三种消息发布服务质量:“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。“至少一次”,确保消息到达,但消息重复可能会发生。“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
第四种:Really S ** ll Message Broker (R ** B) 实现推送,他是一个简单的MQTT代理,同样由IBM提供。
第五种:使用XMPP协议实现Android推送,由于XMPP是开源的,我们能通过自己项目的具体功能,进行高度的定制化,Google使用的C2DM推送服务也是基于该协议实现的,而且现在国外著名的推送平台也支持这种推送方式,比如facebook、msn、Google等等,而国内像QQ也使用类似的协议,由于其源代码并没有开源,我们不知道腾讯使用的是什么即时通讯协议,但是如果国内所有通讯软件都支持XMPP协议,那么陌陌和QQ用户就可以进行聊天了,就像Google talk 能和Facebook用户进行通讯一样。但是这是不可能的,因为腾讯现在在这个行业基本上出于弄断地位。但不管怎么样XMPP协议现在是用在项目中最为合适的及时通讯协议,因此本文也是通过这个协议来进行讲解的。
socket长连接通讯协议是一种网络通讯的规则,相当于客户端和服务器之间有了一种大家都听得懂的交流方式,但是如何实现客户端和服务器之间持久连接,而且确保这个连接是健壮的呢。
通常情况下我们使用的是socket长连接(也可以使用TCP/UDP,MQTT就是基于TCP实现的,但是TCP也是基于socket实现的,所以二者实现原理相同)。而socket连接又分为阻塞式socket和非阻塞式连接,接下来通过简单的例子来讲解二者的区别。当然这些例子可以在我的github上进行下载。
阻塞式的socket长连接服务端:
public class SocketServer { BufferedWriter writer = null; BufferedReader bufferedReader = null; public static void ** in(String[] args) { SocketServer socketServer = new SocketServer(); socketServer.run(); } public void run(){ ServerSocket serverSocket = null; Socket socket = null; try { serverSocket = new ServerSocket(9999); System.out.println("server start,waiting for a socket connect"); //阻塞 直到客户端有链接进入 返回一个socket对象 while (true) { socket =serverSocket.accept(); connectManage(socket); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } public void connectManage(final Socket socket){ new Thread(new Runnable() { @Override public void run() { try { System.out.println("a socket connect :"+socket.hashCode()); bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); // new Timer().schedule(new TimerTask() { // @Override // public void run() { // System.out.println("heart beat once"); // try { // writer.write("heart beat once "); // writer.flush(); // } catch (IOException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // } // }, 3000, 3000); String receivedMsg = null; while ((receivedMsg=bufferedReader.readLine())!=null) { System.out.println(receivedMsg); writer.write("server have received "+socket.hashCode()+" ** ssge "+receivedMsg+" "); writer.flush(); } } catch (IOException e) { e.printStackTrace(); }finally{ try { bufferedReader.close(); socket.close(); writer.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start(); } }客户端:
public class SocketClient { public static void ** in(String[] args) { SocketClient socket = new SocketClient(); socket.run(); } public void run(){ BufferedReader reader =null; BufferedWriter writer = null; BufferedReader inputReader =null; Socket socket = null; try { socket = new Socket("127.0.0.1",9999); writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); reader= new BufferedReader(new InputStreamReader(System.in)); inputReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); startServerReply(inputReader); String content; String receivedMsg ; while(!(content = reader.readLine()).equals("bye")){ writer.write(content+" "); //System.out.println(content); writer.flush(); // receivedMsg=inputReader.readLine(); // System.out.println(receivedMsg); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { writer.close(); reader.close(); socket.close(); inputReader.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void startServerReply(final BufferedReader reader){ new Thread(new Runnable() { @Override public void run() { String response; try { // response = reader.readLine(); // System.out.println("long connect:"+response); while((response = reader.readLine())!=null){ System.out.println("long connect:"+response); } } catch (IOException e) { e.printStackTrace(); }finally{ try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }).start(); } }上面便是一个简单的服务端和客户端阻塞式socket长连接的例子,由于某些语句可能比较长,导致排版比较乱,大家可以拷贝到eclipse和MyEclipse中进行具体分析,建议将服务端代码拷贝到MyEclipse,客户端代码拷贝到eclipse便于分析。具体运行方式是,先启动服务端,再启动客户端,可以在服务端控制台看到有socket连入,此时在客服端输入任意字符,服务端返回对应字符,直到客户端输入bye后结束。这个便是简单的socket通信流程。
但是如何检验该连接是否是长连接呢,上面服务端代码中我注释了一段代码。
new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("heart beat once"); try { writer.write("heart beat once "); writer.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }, 3000, 3000);将这段代码注释去掉,重新运行服务端和客户端,你就可以神奇的发现每隔一段时间服务器就会向客户端发送一个心跳包,证明服务器和客户端的连接一直没有断开,要不然不能随时进行通讯。
那这个长连接如何实现的呢,而且当有多个连接请求时又该怎么办,细心的朋友可能发现了建立长连接的代码。
serverSocket = new ServerSocket(9999); System.out.println("server start,waiting for a socket connect"); //阻塞 直到客户端有链接进入 返回一个socket对象 while (true) { socket =serverSocket.accept(); connectManage(socket); }这段代码的意思是,建立一个socket连接服务,每当有客户端连入服务器时便接受改服务并开启了一个线程对改socket进行处理(connectManage(socket)函数便是开启线程对socket进行处理),而当没有socket请求到达时程序便会阻塞 serverSocket.accept()处,那么为何要放在线程中去处理每个socket呢,原因是当有多个socket请求到达时,不可能将所有socket请求都放在一个线程中进行处理,因此需要开启许多线程进行处理,因此使用while(true)来维持socket长连接,每当客户端有一个socket请求来时,通过serverSocket.accept()获取该socket,阻塞被打破并将该socket交给一个线程去处理,然后又再次进入阻塞状态等待下一个socket请求到达。这就是所谓的阻塞式socket长连接。
这种原生的socket连接方式现在基本不用,原因是,在现实中的项目肯定同时会有大量并发访问,将会同时出现大量的socket请求, 如果针对每个socket请求都建立一个线程去处理,如果服务器同时处理成千上万个socket请求,必定会奔溃。因此这种方式一般不会用在有大量并发访问的情况下。
非阻塞式的长连接当然java作为一门强大的语言肯定会考虑到这种情况,因此java官方专门为此编写了一个完整的类库java.nio(non-blocking),可能很多人并没有听过这个库,没关系百度一下你就知道, nio支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。但是为什么大家都不常用这个库呢,因为这个库的用法着实太难了,语法太复杂,因此有些大牛们就在java.nio上又做了一成封装,形成了各种各样的框架,有些框架现在已经是大名鼎鼎了,比如我接下来要讲的MINA框架就是其中之一。
MINA框架详解首先我们需要将MINA框架集成到我们的服务端代码中,首先在官网:Apache MINA(点击进入)下载最新的MINA包,解压完成后将MINA的核心包mina-core和slf4j包添加到我们的工程目录中即可(当然你也可以将所有的包都加进去),具体的添加过程我就不详细讲解了(亲,百度一下有很多教程)。包添加完成后,就可以进行具体的开发了,之前我们的客户端直接可以使用的,我们只需要将服务端集成MINA即可,使用MINA十分简单,我也通过一个简单的例子讲解。
简易MINA服务器:
public class MinaServer { public static void ** in(String[] args) { try { //定义接受socket对象的 接收器 NioSocketAcceptor acceptor = new NioSocketAcceptor(); //定义消息处理handler acceptor.setHandler(new MinaHander()); //指定解码器 acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory())); //发送心跳包 // acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 5); // acceptor.getSessionConfig().setReaderIdleTime(30); acceptor.bind(new InetSocketAddress(9999)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }消息处理handler:
public class MinaHander extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { System.out.println("exceptionCaught"); } @Override public void messageReceived(IoSession session, Object message) throws Exception { System.out.println("messageReceived:"+(String)message); session.write("mina reply"); } @Override public void messageSent(IoSession session, Object message) throws Exception { System.out.println("messageSent"); } @Override public void sessionClosed(IoSession session) throws Exception { System.out.println("sessionClosed"); } @Override public void sessionCreated(IoSession session) throws Exception { System.out.println("sessionCreated"); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { System.out.println("sessionIdle"); } @Override public void sessionOpened(IoSession session) throws Exception { System.out.println("sessionOpened"); } }拥有上面两个类后,就可以启动服务器了,然后启动客户端进行验证。
从简单的方面说,搭建MINA服务器只需要四步。
第一步:新建 NioSocketAcceptor acceptor = new NioSocketAcceptor()对象,顾名思义该对象意思为 Nio库中socket对象的接受器,专门用来处理socket连接的,当然它是非阻塞的,不用为每个socket连接新建一个线程,只需要接收到socket对象后交给框架处理,那个请求需要处理,框架就处理哪个。具体流程感兴趣的读者可以细读MINA框架的源代码,在推送这个项目中读者知道如何使用该框架即可,如果一一介绍其中的原理,会浪费很多篇幅。
第二步:acceptor.setHandler(new MinaHander())为NioSocketAcceptor 对象添加消息处理handler,MINA框架为了将消息处理和网络处理分离开来,提供了一个专门处理消息的适配器IoHandlerAdapter,所有消息都要通过该适配器来进行处理。适配器相关知识希望读者通过设计模式一书进行详细了解,这里我就不多讲解。
第三步:acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()))指定NioSocketAcceptor对象过滤器和解码器,每个socket请求都必须经过过滤和解码,然后才能对socket中数据进行解析,上面这个new ProtocolCodecFilter(new TextLineCodecFactory())是最简单MINA解码器,只是针对文本解码的,当然你也可以自己编写一些解码器,这里我不过多展开,在我上传到github上的代码中会有几个我自定义的解码器,读者可以尝试使用(注意:如果要使用自定义解码,那么除了服务器要解码以外,同时在客户端也要通过与服务端相同的方式进行加密这样才能在服务端很好的解码)。
第四步:acceptor.bind(new InetSocketAddress(9999))绑定端口,做完以上四步一个简单的MINA服务器便搭建完成。
当然为了测试是长连接,我同样使用的是心跳包的方式,MINA本身就带有心跳包的功能,就是上面服务端我注释掉的代码,将注释去掉运行。
可以看到每隔一段时间都会向客户端发送心跳包,同时客户端也能收到。
对于MINA框架需要解释和了解的东西还有很多,这里只是简单的介绍一下,为之后做推送服务的时候做一个铺垫,在后面的文章中我将紧接着介绍XMPP协议,以及详细的Android推送服务器的实现。
扫码咨询与免费使用
申请免费使用