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
項目總結
以上是生活随笔為你收集整理的Android之基于xmpp openfire smack开发之Android消息推送技术原理分析和实践[4]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android之基于xmpp openf
- 下一篇: Android之Google推荐的图片加