文档章节

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

wangjie142
 wangjie142
发布于 2015/02/22 14:34
字数 3181
阅读 57
收藏 1

顺便也一起回顾下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 plaincopy

  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 plaincopy

  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 plaincopy

  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


本文转载自:http://blog.csdn.net/shimiso/article/details/8156439

共有 人打赏支持
wangjie142
粉丝 5
博文 12
码字总数 7731
作品 0
无锡
程序员
android中实现消息推送(转)

关于服务器端向Android客户端的推送,主要有三种方式:轮询,应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还...

TracyZhang
2012/08/23
0
0
Androidpn 简单实现及分析

(文中部分内容来自网络) XMPP协议: XMPP : The Extensible Messaging andPresence Protocol. 中文全称:可扩展通讯和表示协议. 简介:可扩展通讯和表示协议 (XMPP) 可用于服务类实时通讯、表示...

明舞
2014/08/09
0
0
Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

1、前言 对于广大Android开发者来说,Android O(即Android 8.0)还没玩热,Andriod P(即Andriod 9.0)又要来了。 下图上谷歌官方公布的Android P发布路线图: Android P的最后一个开发者预...

JackJiang2011
08/02
0
0
Android消息推送完美方案

推送功能在手机应用开发中越来越重要,已经成为手机开发的必须。在Android应用开发中,由于众所周知的原因,Android消息推送我们不得不大费周折。本文就是用来和大家共同探讨一种Android消息...

Yujan
2014/04/10
0
0
推送智能、 数据增能,个推助力中国安卓新生态的创建

近日,由国家发改委、工业和信息化部、中国科协指导,中国信息通信研究院主办,个推协办的“2018中国安卓开发者大会”在北京隆重召开。在这场国家级的行业盛会上,相关部委的领导、互联网领域...

个推君
07/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

百度贴吧发送gif图片的限制

百度贴吧中不可以发送超过500k的gif图片,同时尺寸不能超过700个像素。

gugudu
21分钟前
1
0
eclipse中查找所有汉字

今天遇到需要对中文翻译为英文的情况,需要查找一下项目中出现的所有汉字。我们可以使用快捷键Ctrl+H,在搜索一栏输入:[^\x00-\xff],记得勾选正则表达式。 备注: [\x00-\xff] 是 0 - 255的...

hengbao5
22分钟前
2
0
HBase常用操作命令

HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。 HBase是Google Bigtable的开源实现,...

飓风2000
26分钟前
2
0
天创恒达TC TC6C0更新固件包

天创恒达TC TC6C0 升级完 登录密码 admin、000000

yizhichao
28分钟前
2
0
阿里云总裁胡晓明:“这些新杭州故事,明天将会在更多城市发生”

摘要: 9月19日,2018杭州·云栖大会现场,杭州城市大脑2.0正式发布,管辖范围扩大28倍,覆盖面积增至420平方公里,相当于65个西湖大小。 ET城市大脑等数字化城市解决方案,掀开了“杭州故事...

阿里云官方博客
29分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部