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

http://blog.csdn.net/shimiso/article/details/8156439

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

顺便也一起回顾下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配置,导致端口被重复开启两次,最后终于发现问题所在:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"  xmlns:util="http://www.springframework.org/schema/util"  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">  <context:component-scan base-package="org.androidpn.server.*" /><!-- 自动装配 -->    <!-- =============================================================== -->  <!-- Resources                                                       -->  <!-- =============================================================== -->  <bean id="propertyConfigurer"  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  <property name="locations">  <list>  <value>classpath:jdbc.properties</value>  </list>  </property>  </bean>  <!-- =============================================================== -->  <!-- Data Source                                                     -->  <!-- =============================================================== -->  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  destroy-method="close">  <property name="driverClassName" value="${jdbcDriverClassName}" />  <property name="url" value="${jdbcUrl}" />  <property name="username" value="${jdbcUsername}" />  <property name="password" value="${jdbcPassword}" />  <property name="maxActive" value="${jdbcMaxActive}" />  <property name="maxIdle" value="${jdbcMaxIdle}" />  <property name="maxWait" value="${jdbcMaxWait}" />  <property name="defaultAutoCommit" value="true" />  </bean>   <!-- sessionFactory -->  <bean id="sessionFactory"  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">  <property name="dataSource" ref="dataSource" />  <property name="configLocation" value="classpath:hibernate.cfg.xml" />  </bean>  <!-- 配置事务管理器 -->  <bean id="txManager"  class="org.springframework.orm.hibernate3.HibernateTransactionManager">  <property name="sessionFactory" ref="sessionFactory" />  <property name="dataSource" ref="dataSource" />  </bean>  <!-- 采用注解来管理事务-->  <tx:annotation-driven transaction-manager="txManager" />   <!-- spring hibernate工具类模板 -->  <bean id="hibernateTemplate"  class="org.springframework.orm.hibernate3.HibernateTemplate">  <property name="sessionFactory" ref="sessionFactory"></property>  </bean>  <!-- spring jdbc 工具类模板 -->  <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">  <property name="dataSource">  <ref bean="dataSource" />  </property>  </bean>      <!-- =============================================================== -->  <!-- SSL                                                             -->  <!-- =============================================================== -->  <!--  <bean id="tlsContextFactory"  class="org.androidpn.server.ssl2.ResourceBasedTLSContextFactory">  <constructor-arg value="classpath:bogus_mina_tls.cert" />  <property name="password" value="boguspw" />  <property name="trustManagerFactory">  <bean class="org.androidpn.server.ssl2.BogusTrustManagerFactory" />  </property>  </bean>  -->  <!-- MINA  -->   <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">  <property name="customEditors">  <map>  <entry key="java.net.SocketAddress">  <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" />  </entry>  </map>  </property>  </bean>  <bean id="xmppHandler" class="org.androidpn.server.xmpp.net.XmppIoHandler" />  <bean id="filterChainBuilder"  class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">  <property name="filters">  <map>  <entry key="executor">  <bean class="org.apache.mina.filter.executor.ExecutorFilter" />  </entry>  <entry key="codec">  <bean class="org.apache.mina.filter.codec.ProtocolCodecFilter">  <constructor-arg>  <bean class="org.androidpn.server.xmpp.codec.XmppCodecFactory" />  </constructor-arg>  </bean>  </entry>  <!--  <entry key="logging">  <bean class="org.apache.mina.filter.logging.LoggingFilter" />  </entry>  -->  </map>  </property>  </bean>  <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor"  init-method="bind" destroy-method="unbind" scope="singleton">  <property name="defaultLocalAddress" value=":5222" />  <property name="handler" ref="xmppHandler" />  <property name="filterChainBuilder" ref="filterChainBuilder" />  <property name="reuseAddress" value="true" />  </bean>  <bean id="serviceLocator" class="org.androidpn.server.service.ServiceLocator" scope="singleton" />   <!-- Services-->   <bean id="userService" class="org.androidpn.server.service.impl.UserServiceImpl"/>  <bean id="notificationService" class="org.androidpn.server.service.impl.NotificationServiceImpl"/>  </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服务器。

package org.androidpn.server.service;  import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.BeanFactory;  
import org.springframework.beans.factory.BeanFactoryAware;  public class ServiceLocator implements BeanFactoryAware {  private static BeanFactory beanFactory = null;  private static ServiceLocator servlocator = null;  public static String USER_SERVICE = "userService";  public static String NOTIFICATION_SERVICE = "notificationService";  public void setBeanFactory(BeanFactory factory) throws BeansException {  this.beanFactory = factory;  }  public BeanFactory getBeanFactory() {  return beanFactory;  }  public static ServiceLocator getInstance() {  if (servlocator == null)  servlocator = (ServiceLocator) beanFactory.getBean("serviceLocator");  return servlocator;  }  /** * 根据提供的bean名称得到相应的服务类 *  * @param servName *            bean名称 */  public static Object getService(String servName) {  return beanFactory.getBean(servName);  }  /** * 根据提供的bean名称得到对应于指定类型的服务类 *  * @param servName *            bean名称 * @param clazz *            返回的bean类型,若类型不匹配,将抛出异常 */  public static Object getService(String servName, Class clazz) {  return beanFactory.getBean(servName, clazz);  }  /** * Obtains the user service. *  * @return the user service */  public static UserService getUserService() {  return (UserService) getService(USER_SERVICE);  }  public static NotificationService getNotificationService() {  return (NotificationService) getService(NOTIFICATION_SERVICE);  }  
}  
在config.properties中还要特别注意xmpp.resourceName必须跟客户端中XmppManager的private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";保持一致,否则连不上服务器,还xmpp.session.maxInactiveInterval=-1表示永不中断,如果设定了时间超过这个时间范围没有任何活动就会自动断开,这里的时间单位全部是毫秒。

apiKey=1234567890  
xmpp.ssl.storeType=JKS  
xmpp.ssl.keystore=conf/security/keystore  
xmpp.ssl.keypass=changeit  
xmpp.ssl.truststore=conf/security/truststore  
xmpp.ssl.trustpass=changeit  
xmpp.resourceName=AndroidpnClient  ##Added by ken  
username=admin  
password=admin  #资源名称  
resource_name=AndroidpnClient  #校验超时时间间隔  
xmpp.session.checkTimeoutInterval=10000  #Session timeout最大非活动时间间隔  
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日更新)

下载地址:http://download.csdn.net/detail/shimiso/5267500

项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294592.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

12年前的高考到底有多难,只在这一道题上就看出来了...

▲ 点击查看2008年高考江西数学考卷的最后一题&#xff0c;说是高考史上最恐怖的数学题&#xff0c;应该没有异议。这道题到底有多难呢&#xff1f;最后这道压轴题一共是14分。考试结果出来&#xff0c;所有考生的平均分是0.31分。曾有一位同学这样介绍&#xff1a;“在我们学校…

Cypher查询语言--Neo4j-WHERE(三)

目录 WhereBoolean 操作类型节点属性上的过滤正则表达式转义正则表达式不分大小些正则表达式关系类型上的过滤属性存在性如果缺失属性默认为true如果缺失属性默认为false空置null过滤关系过滤Where 如果需要从查找的数据的图中过滤&#xff0c;可以在查询语句中添加where子句。…

12篇学通C#网络编程——第一篇 基础之进程线程

在C#的网络编程中&#xff0c;进程和线程是必备的基础知识&#xff0c;同时也是一个重点&#xff0c;所以我们要好好的掌握一下。 一&#xff1a;概念 首先我们要知道什么是”进程”&#xff0c;什么是“线程”&#xff0c;好&#xff0c;查一下baike。 进程&#xff1a;是一个…

建立学生选课表 mysql 语句_MySQL常用SQL语句(Python实现学生、课程、选课表增删改查)...

以基本的学生选课为例&#xff0c;建立选课数据库&#xff0c;学生、班级、选课信息三张表&#xff0c;并分别对表进行插删改操作&#xff1a;import MySQLdbtry:conn MySQLdb.connect(host localhost, user root, passwd root, db xuanke, port 3306)cur conn.cursor()…

加快网站访问速度--jquery.js

jquery现在是越来越大&#xff0c;网络加载速度上我们应该做到能省就省&#xff0c;毫无疑问google的服务器和cdn以及访问速度是非常快的&#xff0c;而且google敞开怀抱&#xff0c;提供各种代码库给我们下载调用。jquery就是其中一个。 在jquery官网有从google 微软microsoft…

也谈程序员的35岁危机

前言本来这期要推一篇观察者模式和发布订阅模式的技术文给各位看官(在写了)&#xff0c;但无奈最近爱奇艺裁员事件引起了轩然大波&#xff0c;互联网上和各种技术群又展开了轰轰烈烈的讨论&#xff0c;每位IT从业者都不能独善其身。那么今天这一期我们就聊聊程序员的35岁危机究…

豆瓣评分9.4!这部大片你不应该错过,每一秒都是不敢看的残忍!

全世界只有3.14 % 的人关注了爆炸吧知识人类占据了地球上绝大多数宜居的地方&#xff0c;我们面对着温柔的地球母亲&#xff0c;但对野生动物们来说&#xff0c;地球却是一个水深火热的星球。你觉得你已经一无所有了&#xff0c;你觉得生活的负荷已经让你难以前进了&#xff1b…

Unity3D4.* NGUI制作动态字库

新建一个工程&#xff0c;这个工程必须没有中文路径&#xff0c;否则会不识别字体&#xff01;&#xff01;&#xff01; 首先导入NGUI插件&#xff0c;这里我用的是NGUI 3.0.2版本的。 在Assets 下创建一个文件夹&#xff0c;用来存放接下来的工作文件 。 这里随便选择一种字体…

java解析json_JAVA解析JSON数据

在使用第三方api的使用&#xff0c;有时候会从网络中获得json数据&#xff0c;所以说我们将如何解析json数据&#xff1f;下面小编将通过以下几点来进行json的讲解JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read…

Android之android studio如何解决‘:app:packageDebug‘.(Duplicate files copied in APK META-INF/DEPENDENCIES)

不废话&#xff0c;先爆照 今天在使用glide的时候&#xff0c;我在项目里面添加了httpcore-4.3.2.jar和4.3.5.jar包&#xff0c;但是当我运行的时候就出现了这个错误 然后在build.gradle里面配置下面的信息就好了&#xff0c; android { packagingOptions { exclude META-IN…

Asp.Net MVC4.0 官方教程 入门指南之一-- 入门介绍

本教程将为您讲解使用微软的Visual Studio 2012 来建立一个ASP.NET MVC4 Web应用程序所需要的基础知识。 本示例将构建什么样的应用程序&#xff1f; 您将实现一个简单的电影管理应用程序&#xff0c;此程序将从数据库中选取记录展示列表&#xff0c;支持查询和查看&#xff0…

关注!这所211高校通知不放寒假!校园将实行封闭管理!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自&#xff1a;募格学术新年伊始&#xff0c;北京顺义&#xff0c;辽宁大连、沈阳&#xff0c;黑龙江黑河&#xff0c;河北石家庄、邢台等地相继报告新增本土病例&#xff0c;随着春节的临近&#xff0c;人员流动和聚集增加&#x…

MediatR 在.NET应用中的实践

MediatR 简介MediatR是.NET中的开源简单中介者模式实现.它通过一种进程内消息传递机制&#xff08;无其他外部依赖&#xff09;&#xff0c;进行请求/响应、命令、查询、通知和事件的消息传递&#xff0c;并通过泛型来支持消息的智能调度。开源库地址是https://github.com/jbog…

java 录屏_java 录屏 小工具源码(idea)

【实例简介】录制的视频保存在 java.io.tmpdir 目录&#xff0c;windows通常为 C:\Users\Administrator\AppData\Local\Temp【实例截图】点击播放后&#xff0c;效果如下&#xff1a;【核心代码】import java.awt.AWTException;import java.awt.Color;import java.awt.Dimensio…

Java - 强引用、弱引用、软引用、虚引用

1、强引用&#xff08;StrongReference&#xff09; 强引用是使用最普遍的引用。如果一个对象具有强引用&#xff0c;那垃圾回收器绝不会回收它。如下&#xff1a; Object onew Object(); // 强引用 当内存空间不足&#xff0c;Java虚拟机宁愿抛出OutOfMemoryError错误&am…

dede织梦5.7,后台采集数据导入,空文章过滤.

为什么80%的码农都做不了架构师&#xff1f;>>> 后台目录下 dede/co_export.php 186行左右 else if($itemName litpic){$litpic trim($ctag->GetInnerText());} } 下面添加&#xff0c;变成 else if($itemName litpic){$litpic trim($ctag->GetInnerT…

真正的男人要勇于承担责任......

1 下个月可以住贵一点的房子了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 当灯牌坏了一个&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 当前男友提着钱上门▼4 真正的男人要勇于承担责任&#xff08;via.豆瓣 pink&#xff09;▼5 &#xff1f…

WPF里面的常用笔刷

程序运行效果 <Window x:Class"This_brush.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"Title"MainWindow" Height"350" Width…

没想到,错误的单例写法,让 RabbitMQ 大量超时导致程序挂死!

一&#xff1a;背景 1. 讲故事10月份星球里的一位老朋友找到我&#xff0c;说他们公司的程序在一个网红直播带货下给弄得无响应了&#xff0c;无响应期间有大量的 RabbitMQ 超时&#xff0c;寻求如何找到根源&#xff0c;聊天截图我就不发了。既然无响应了&#xff0c;那必然是…

Android之OKHttp使用总结

介绍: OkHttp是一个高效的HTTP库: 持 SPDY ,共享同一个Socket来处理同一个服务器的所有请求如果SPDY不可用,则通过连接池来减少请求延时无缝的支持GZIP来减少数据流量缓存响应数据来减少重复的网络请求 会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址…