基于xmpp openfire smack开发之Android消息推送技术原理分析和实践[4]

前面几篇给大家系统讲解的有关xmpp openfire smack asmack相关的技术和使用,大家如果有所遗忘可以参考

基于xmpp openfire smack开发之openfire介绍和部署[1]

基于xmpp openfire smack开发之smack类库介绍和使用[2]

基于xmpp openfire smack开发之Android客户端开发[3]

 

顺便也一起回顾下xmpp的历程

xmpp协议起源于著名的Linux即时通讯服务服务器jabber,有时候我们会把xmpp协议也叫jabber协议,其实这是不规范的,xmpp是个协议,而jabber是个服务器,因为jabber开源,设计精良,安全,稳定,跨语言,跨平台,封装开发简便,越来越多人开始使用它,并且逐步完善,不久它便形成了一个强大的标准化体系,Google GTalk、Pidgin、PSI、Spark、Pandion、MSN、Yahoo、ICQ..诸如此类一些软件在这个强大的标准体系下实现了互联.那么XMPP到底是什么意思,用通俗的话讲它和基于xml格式的一些协议原理差不多,只不过是个针对服务器的软件协议罢了。

那么在java领域是否存在一个类似jabber那么强大开源稳定的也完美支持xmpp协议的服务器呢?答案有的,那便是openfire,openfire是纯java开发的基于XMPP的协议,目前最终版本锁定在了2011年openfire 3.7,它一共有linux windows mac 三个版本,安装也非常简单,openfire这个服务器是个开放式的平台,它内部集成的服务包括即时通讯服务,会议室服务,用户安全验证和管理服务,搜索服务,组织机构服务,会话服务,这几大服务都有相应的管理类和对外接口,它的二次开发和扩展都是在插件基础上直接嫁接进去的,早期有很多第三方为他做了插件,有语音服务,red5视频服务,邮件服务等等,语音和视频在openfire上一直是个鸡肋,没有非常好的解决方案,而做这些插件的大部分都停止更新,大家如果选用openfire做视频和语音还要慎重!抛开这些插件,openfire在IM及时通讯上还是相当强大稳定的,不少公司拿它来做二次开发!但即便如此openfire的二次开发成本还是比较高昂的,笔者曾经成功费了九牛二虎之力将源码环境搭建起来,并成功将它与我们JAVAEE 经典架构SSH成功组装,用openfire的桌面客户端spark软件和android开源xmpp客户端Beam软件,web端聊天软件Claros Chat享受了一把在自己服务器上“随时随地聊天”,不过这些都是实验阶段,距离成熟可用还很远!研究技术可以这么勾兑尝试,真的给人用可不能这么随意,我们还是要挖掘真正对我们有用的价值!

openfire过于庞大繁复,许多对我们来说都是没什么用的,甚至要砍掉改造,能不能有精简的xmpp服务器呢?答案是有的,androidpn,笔者认真比对过openfire和androidpn的源码,最后惊奇的发现,原来它就是从openfire里面庖丁解牛出来的一部分,做这件事的人非常的了不起,为我们省了很大力气,在此感谢他的开源和共享精神,那么androidpn分离出来的是消息推送服务,简言之就是从服务端向android客户端推送消息的服务,因为openfire的源码架构是在jetty基础上建立的,它的启动和部署方式和我们传统的服务器tomcat和weblogic等有点区别,所以androidpn也有jetty的影子,在和我们传统架构组合的时候还要再把它和jetty拆开, androidpn的搭建和使用网上的教程很多,大家可以发现大部分千篇一律,出现一个OK界面就没了,堂而皇之的写上原创,有的只是改了下hello world,如此糊弄,实在难为所用!

androidpn消息推送采用的是apache的mina框架做的,服务端和客户端两边都有监听,也就是我们所说的socket编程,有人说socket编程有什么难的,就那么回事,其实不然,我们平时写的socket聊天都只是在局域网的,但是要穿透路由和防火墙,让信息安全及时的传送到另一个网关的局域网电脑中,就不是一件简单的活了,其中涉及到在nat上打洞,还有线程,断网重连,安全加密等等,那么androidpn配合mina相当于把这些活都干了,那么我们要的干活就相对比较精细了,第一学习mina的安装配置的规则,第二学习xmpp协议组装和解析的规则,第三学习androidpn推和收消息的核心代码,如此三点我们便能灵活驾驭住androidpn出现再大的问题自己也能动手去调了。

   

在和spring整合的时候大家要注意不要让mina服务启动2次,笔者整合时候无意发现在linux64位系统,weblogic上启动时候总是报5222已经被占用,反复查看代码发现mina在随web容器启动过一次5222端口后,xmppserver类中的start方法中ClassPathXmlApplicationContext类又加载了一次spring配置,导致端口被重复开启两次,最后终于发现问题所在:

[html] view
plain
copy

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"  
  5.     xmlns:util="http://www.springframework.org/schema/util"  
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  7.         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd  
  8.         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd  
  9.         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
  10.         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">  
  11.     <context:component-scan base-package="org.androidpn.server.*" /><!-- 自动装配 -->    
  12.   
  13.     <!-- =============================================================== -->  
  14.     <!-- Resources                                                       -->  
  15.     <!-- =============================================================== -->  
  16.     <bean id="propertyConfigurer"  
  17.         class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  18.         <property name="locations">  
  19.             <list>  
  20.                 <value>classpath:jdbc.properties</value>  
  21.             </list>  
  22.         </property>  
  23.     </bean>  
  24.   
  25.     <!-- =============================================================== -->  
  26.     <!-- Data Source                                                     -->  
  27.     <!-- =============================================================== -->  
  28.   
  29.     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  
  30.         destroy-method="close">  
  31.         <property name="driverClassName" value="${jdbcDriverClassName}" />  
  32.         <property name="url" value="${jdbcUrl}" />  
  33.         <property name="username" value="${jdbcUsername}" />  
  34.         <property name="password" value="${jdbcPassword}" />  
  35.         <property name="maxActive" value="${jdbcMaxActive}" />  
  36.         <property name="maxIdle" value="${jdbcMaxIdle}" />  
  37.         <property name="maxWait" value="${jdbcMaxWait}" />  
  38.         <property name="defaultAutoCommit" value="true" />  
  39.     </bean>   
  40.       
  41.     <!-- sessionFactory -->  
  42.     <bean id="sessionFactory"  
  43.         class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  
  44.         <property name="dataSource" ref="dataSource" />  
  45.         <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
  46.     </bean>  
  47.   
  48.     <!-- 配置事务管理器 -->  
  49.     <bean id="txManager"  
  50.         class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
  51.         <property name="sessionFactory" ref="sessionFactory" />  
  52.         <property name="dataSource" ref="dataSource" />  
  53.     </bean>  
  54.       
  55.     <!-- 采用注解来管理事务-->  
  56.     <tx:annotation-driven transaction-manager="txManager" />   
  57.       
  58.     <!-- spring hibernate工具类模板 -->  
  59.     <bean id="hibernateTemplate"  
  60.         class="org.springframework.orm.hibernate3.HibernateTemplate">  
  61.         <property name="sessionFactory" ref="sessionFactory"></property>  
  62.     </bean>  
  63.     <!-- spring jdbc 工具类模板 -->  
  64.     <bean id="jdbcTemplate"  
  65.         class="org.springframework.jdbc.core.JdbcTemplate">  
  66.         <property name="dataSource">  
  67.             <ref bean="dataSource" />  
  68.         </property>  
  69.     </bean>      
  70.       
  71.     <!-- =============================================================== -->  
  72.     <!-- SSL                                                             -->  
  73.     <!-- =============================================================== -->  
  74.   
  75.     <!--  
  76.     <bean id="tlsContextFactory"  
  77.         class="org.androidpn.server.ssl2.ResourceBasedTLSContextFactory">  
  78.         <constructor-arg value="classpath:bogus_mina_tls.cert" />  
  79.         <property name="password" value="boguspw" />  
  80.         <property name="trustManagerFactory">  
  81.             <bean class="org.androidpn.server.ssl2.BogusTrustManagerFactory" />  
  82.         </property>  
  83.     </bean>  
  84.     -->  
  85.     <!-- MINA  -->   
  86.     <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">  
  87.         <property name="customEditors">  
  88.             <map>  
  89.                 <entry key="java.net.SocketAddress">  
  90.                     <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" />  
  91.                 </entry>  
  92.             </map>  
  93.         </property>  
  94.     </bean>  
  95.   
  96.     <bean id="xmppHandler" class="org.androidpn.server.xmpp.net.XmppIoHandler" />  
  97.   
  98.     <bean id="filterChainBuilder"  
  99.         class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">  
  100.         <property name="filters">  
  101.             <map>  
  102.                 <entry key="executor">  
  103.                     <bean class="org.apache.mina.filter.executor.ExecutorFilter" />  
  104.                 </entry>  
  105.                 <entry key="codec">  
  106.                     <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter">  
  107.                         <constructor-arg>  
  108.                             <bean class="org.androidpn.server.xmpp.codec.XmppCodecFactory" />  
  109.                         </constructor-arg>  
  110.                     </bean>  
  111.                 </entry>  
  112.                 <!--  
  113.                 <entry key="logging">  
  114.                     <bean class="org.apache.mina.filter.logging.LoggingFilter" />  
  115.                 </entry>  
  116.                 -->  
  117.             </map>  
  118.         </property>  
  119.     </bean>  
  120.   
  121.     <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"  
  122.         init-method="bind" destroy-method="unbind" scope="singleton">  
  123.         <property name="defaultLocalAddress" value=":5222" />  
  124.         <property name="handler" ref="xmppHandler" />  
  125.         <property name="filterChainBuilder" ref="filterChainBuilder" />  
  126.         <property name="reuseAddress" value="true" />  
  127.     </bean>  
  128.        
  129.       
  130.     <bean id="serviceLocator" class="org.androidpn.server.service.ServiceLocator" scope="singleton" />   
  131.   
  132.       
  133.     <!-- Services-->   
  134.       
  135.     <bean id="userService" class="org.androidpn.server.service.impl.UserServiceImpl"/>  
  136.       
  137.     <bean id="notificationService" class="org.androidpn.server.service.impl.NotificationServiceImpl"/>  
  138.        
  139. </beans>  

配置serviceLocator是为了保证spring容器只能由一个上下文,也就是spring容器只被启动一次,我们将BeanFactory交给了serviceLocator,这样一来有什么好处呢?

控制层,服务层,数据库操作层都受spring管理,在他们中去跟spring要资源,一定是要什么有什么想怎么拿就怎么拿,都很方便,但是如果想在没有被spring所管理的类中去拿spring的资源,动作就不那么优雅了,有人建议用ClassPath加载器初始化spring工厂来获取资源,问题就处在这里,这种做法必定会产生2个spring上下文,一个是web容器所启动的,一个是java类加载器所启动的,我们的MINA服务器也就被启动了2次,其实资源被重复多次实例化除了影响性能外,对程序影响可能并不大,但是MINA被启动2次,肯定会出问题的。为保证spring只有一个上下文,我们将容器上下文交给了serviceLocator,脱离spring管控的环境可以面向serviceLocator来调度spring中的资源操作MINA服务器。

[java] view
plain
copy

  1. package org.androidpn.server.service;  
  2.   
  3. import org.springframework.beans.BeansException;  
  4. import org.springframework.beans.factory.BeanFactory;  
  5. import org.springframework.beans.factory.BeanFactoryAware;  
  6.   
  7.    
  8. public class ServiceLocator implements BeanFactoryAware {  
  9.     private static BeanFactory beanFactory = null;  
  10.   
  11.     private static ServiceLocator servlocator = null;  
  12.   
  13.     public static String USER_SERVICE = "userService";  
  14.   
  15.     public static String NOTIFICATION_SERVICE = "notificationService";  
  16.   
  17.     public void setBeanFactory(BeanFactory factory) throws BeansException {  
  18.     this.beanFactory = factory;  
  19.     }  
  20.   
  21.     public BeanFactory getBeanFactory() {  
  22.     return beanFactory;  
  23.     }  
  24.   
  25.     public static ServiceLocator getInstance() {  
  26.     if (servlocator == null)  
  27.         servlocator = (ServiceLocator) beanFactory.getBean("serviceLocator");  
  28.     return servlocator;  
  29.     }  
  30.   
  31.     /** 
  32.      * 根据提供的bean名称得到相应的服务类 
  33.      *  
  34.      * @param servName 
  35.      *            bean名称 
  36.      */  
  37.     public static Object getService(String servName) {  
  38.     return beanFactory.getBean(servName);  
  39.     }  
  40.   
  41.     /** 
  42.      * 根据提供的bean名称得到对应于指定类型的服务类 
  43.      *  
  44.      * @param servName 
  45.      *            bean名称 
  46.      * @param clazz 
  47.      *            返回的bean类型,若类型不匹配,将抛出异常 
  48.      */  
  49.     public static Object getService(String servName, Class clazz) {  
  50.     return beanFactory.getBean(servName, clazz);  
  51.     }  
  52.   
  53.     /** 
  54.      * Obtains the user service. 
  55.      *  
  56.      * @return the user service 
  57.      */  
  58.     public static UserService getUserService() {  
  59.     return (UserService) getService(USER_SERVICE);  
  60.     }  
  61.   
  62.     public static NotificationService getNotificationService() {  
  63.     return (NotificationService) getService(NOTIFICATION_SERVICE);  
  64.     }  
  65. }  

在config.properties中还要特别注意xmpp.resourceName必须跟客户端中XmppManager的private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";保持一致,否则连不上服务器,还xmpp.session.maxInactiveInterval=-1表示永不中断,如果设定了时间超过这个时间范围没有任何活动就会自动断开,这里的时间单位全部是毫秒。

[plain] view
plain
copy

  1. apiKey=1234567890  
  2. xmpp.ssl.storeType=JKS  
  3. xmpp.ssl.keystore=conf/security/keystore  
  4. xmpp.ssl.keypass=changeit  
  5. xmpp.ssl.truststore=conf/security/truststore  
  6. xmpp.ssl.trustpass=changeit  
  7. xmpp.resourceName=AndroidpnClient  
  8.   
  9. ##Added by ken  
  10. username=admin  
  11. password=admin  
  12.   
  13. #资源名称  
  14. resource_name=AndroidpnClient  
  15.   
  16. #校验超时时间间隔  
  17. xmpp.session.checkTimeoutInterval=10000  
  18.   
  19. #Session timeout最大非活动时间间隔  
  20. xmpp.session.maxInactiveInterval=1000000  

在androidpn.properties中端口和IP不要写错,有人喜欢写localhost,在手机上是无法识别的,必须写绝对IP地址。

apiey=1234567890

xmppHost=192.168.1.78

xmppPort=5222

 

运行结果如下:

离线消息也支持,先给离线用户发个消息,效果如下:

在数据库中我们看到有一条离线消息是发给用户4aa50dde313f4b63907c2430bf00b413,status为0标记为离线

这时我们再上线,大约等待20秒左右,查看系统控制台打印:

查看android端看看用户4aa50dde313f4b63907c2430bf00b413上线情况:

这时候数据库记录发生了变化,status变成了2,表示已经接收,用户点击OK的时候,它又变成了3表示已经查看

离线消息的原理相对比较简单,当系统给指定用户发送消息时候,会首先判断用户是够在线,如果在线就直接发送,如果没有在线就暂时标记保存,等用户上线时候先查离线消息然后弹出,其实整个项目都是开源的,可能唯一的难点就是对MINA和XMPP协议的不了解,再加上本身对socket和多线程的畏惧,如果这些全部都掌握,驾驭好这套源码还是很有信心的,了解其基本原理以后,我们就可以放心的做更多的扩展。

         网上现在也有不少androidpn版本,五花八门什么都有,里面到底有没问题,改了什么没改什么都不知道,基本上已经追溯不到原创到底是谁了,索性就只能从国外的一个网站上下了一个比较可靠的版本自己动手去量身改造,终于出了一个比较稳定版本。对于消息提醒来说,它仅仅是个notification,许多人非要把业务数据也做进去,更有夸张好几兆的xml数据就这么硬塞提醒过去,这种做法本身就背离了设计的初衷,非要把跑车当牛车使能不出问题吗?其实业务数据还是用http拉比较好,xmpp及时的前提是用资源消耗作为代价的,我们能适度就适度用,用好用稳就行!

项目源码下载: Androidpn威力加强版(4月17日更新)

搭建步骤:
1.android端找到res/raw/androidpn.properties文件修改服务器ip地址,不要写localhost,写绝对ip地址
2.服务端找到resources/jdbc.properties 在mysql中新建一个数据库apn,并将连接指向该库,设置用户名和密码,库表会随服务启动的时候自动创建
3.先启动服务,再打开android客户端,点击连接即可

参阅文献
Openfirehttp://www.igniterealtime.org/
push-notificationhttp://www.push-notification.org/
Claros chathttp://www.claros.org/
androidpnsourceforgehttp://sourceforge.net/projects/androidpn/
android消息推送解决方案http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378971.html
xmpp协议实现原理介绍 http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378956.html

时间: 2014-11-11

基于xmpp openfire smack开发之Android消息推送技术原理分析和实践[4]的相关文章

基于xmpp openfire smack开发之Android客户端开发[3]

在上两篇文章中,我们依次介绍openfire部署以及smack常用API的使用,这一节中我们着力介绍如何基于asmack开发一个Android的客户端,本篇的重点在实践,讲解和原理环节,大家可以参考前两篇的文章 基于xmpp openfire smack开发之openfire介绍和部署[1] 基于xmpp openfire smack开发之smack类库介绍和使用[2]   1.源码结构介绍 activity包下存放一些android页面交互相关的控制程序,还有一个些公共帮助类 db包为sqli

基于xmpp openfire smack开发之openfire介绍和部署

前言 Java领域的即时通信的解决方案可以考虑openfire+spark+smack.当然也有其他的选择. Openfire是基于Jabber协议(XMPP)实现的即时通信服务器端版本,目前建议使用3.8.1版本,这个版本是当前最新的版本,而且网上可以找到下载的源代码. 即时通信客户端可使用spark2.6.3,这个版本是目前最新的release版本,经过测试发现上一版本在视频支持,msn网关支持上可能有问题,所以选择openfire3.8.1+spark2.6.3是最合适的选择. Smack

消息推送的问题-Android开发之bmob消息推送

问题描述 Android开发之bmob消息推送 bmob实现消息推送时,部分手机收不到什么鬼?不论是指定或是发送所有手机. 解决方案 你检查过那些手机是否有网络吗? 解决方案二: 看看官文文档 介绍 或者换极光推动 或者 友盟的推送 解决方案三: 只是个别手机有问题,说明通道应该没问题,检查手机环境,或者联系服务商咨询

android消息推送-Android消息推送干什么用呀?

问题描述 Android消息推送干什么用呀? Android的消息推送干什么用呀?是从服务器推送过来的么?还是服务器有更新之后,手机端解析了数据之后,在用户打来软件的时候再推送? 解决方案 服务器调用第三方的接口,推送到你手机上,提醒你某个业务有新动作了,即使app不启动,也是可以推送的,微信,qq你总该用过吧 解决方案二: android消息推送Android消息推送Android中的消息推送 解决方案三: 信鸽和JPush都挺好用的

Android、iOS和Windows Phone中的推送技术

推送并不是什么新技术,这种技术在互联网时代就已经很流行了.只是随着进入移动互联网时代,推送技术显得更加重要.因为在智能手机中,推送从某种程度上,可以取代使用多年的短信,而且与短信相比,还可以向用户展示更多的信息(如图像.表格.声音等). 推送技术的实现通常会使用服务端向客户端推送消息的方式.也就是说客户端通过用户名.Key等ID注册到服务端后,在服务端就可以将消息向所有活动的客户端发送. 实际上,在很多移动操作系统中,官方都为其提供了推送方案,例如,Google的云推送.IOS.Windows 

浅析Android、iOS和Windows Phone中的推送技术

推送并不是什么新技术,这种技术在互联网时代就已经很流行了.只是随着进入移动互联网时代,推送技术显得更加重要.因为在智能手机中,推送从某种程度上,可以取代使用多年的短信,而且与短信相比,还可以向用户展示更多的信息(如图像.表格.声音等). 推送技术的实现通常会使用服务端向客户端推送消息的方式.也就是说客户端通过用户名.Key等ID注册到服务端后,在服务端就可以将消息向所有活动的客户端发送. 实际上,在很多移动操作系统中,官方都为其提供了推送方案,例如,Google的云推送.IOS.Windows

IOS 基于APNS消息推送原理与实现(JAVA后台)

IOS 基于APNS消息推送原理与实现(JAVA后台) Push的原理: Push 的工作机制可以简单的概括为下图   图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple Push Notification Service(Apple Push服务器)的缩写,是苹果的服务器. 上图可以分为三个阶段. 第一阶段:Push服务器应用程序把要发送的消息.目的iPhone的标识打包,发给APNS. 第二阶段:APNS在自

Android实现推送方式解决方案

原文:http://www.cnblogs.com/hanyonglu/archive/2012/03/04/2378971.html 本文介绍在Android中实现推送方式的基础知识及相关解决方案.推送功能在手机开发中应用的场景是越来起来了,不说别的,就我们手机上的新闻客户端就时不j时的推送过来新的消息,很方便的阅读最新的新闻信息.这种推送功能是好的一面,但是也会经常看到很多推送过来的垃圾信息,这就让我们感到厌烦了,关于这个我们就不能多说什么了,毕竟很多商家要做广告.本文就是来探讨下Andro

iOS 和 Android 的后台推送工作原理各是如何?有什么区别?

iOS 的推送iOS 在系统级别有一个推送服务程序使用 5223 端口.使用这个端口的协议源于 Jabber 后来发展为 XMPP ,被用于 Gtalk 等 IM 软件中.所以, iOS 的推送,可以不严谨的理解为:苹果服务器朝手机后台挂的一个 IM 服务程序发送的消息.然后,系统根据该 IM 消息识别告诉哪个 Apps 具体发生了什么事.然后,系统分别通知这些 Apps . 这个消息的内容是这样的:应该说,苹果这种方式在技术上没有什么创新.但是,整个架构是很了不起的. 因为:1 使用久经考验的