天天看點

即時通訊軟體openfire+spark+smack

是以我基本上分為三篇文章來介紹此類軟體的開發:

第一篇是關于xmpp 協定是啥,im 是啥以及一個比較有名的開源實作,該開源實作包括三個部分(spark、smack和openfire);

第二篇講如何開發基于spark 的用戶端im 插件部分;

第三篇講如何開發基于openfire 伺服器端的插件部分。

好了,進入正題吧。

什麼是xmpp?

extensible messaging and presence protocol,簡單的來講,它就是一個發送接收處理消息的協定,但是這個協定發送的消息,既不是二進制的東東也不是字元串,而是xml。正是因為使用了xml作為消息傳遞的中介,extensible 才談的上,不是麼?嘿嘿。再詳盡的東西,我也就不多介紹了,大家可以去百度百科裡檢視下。

什麼是im ?

instant messenger,及時通信軟體,就是大家使用的qq、msn messenger和gtalk等等。其中gtalk 就是基于xmpp 協定的一個實作,其他的則不是。目前im 幾乎作為每個上網者必然使用的工具,在國外的大型企業中有一些企業級的im應用,但是其商業價值還沒完全發揮出來。設想既然xmpp 協定是一個公開的協定,那麼每個企業都可以利用它來開發适合本身企業工作,提高自身生産效率的im;甚至,你還可以在網絡遊戲中內建這種通信軟體,不但讓你可以邊遊戲邊聊天,也可以開發出适合遊戲本身的im

應用,比如說一些遊戲關鍵場景提醒功能,團隊語音交流等等都可以基于im來實作。說了這麼多,就是一個意思,其商業價值遠遠比你想的高!

spark smack 和 openfire

開源界總是有許多有趣的東東,這三個合起來就是一個完整的xmpp im 實作。包括伺服器端——openfire,用戶端——spark,xmpp 傳輸協定的實作——smack(記住,xmpp是一個協定,協定是需要實作的,smack起到的就是這樣的一個作用)。三者都是基于java 語言的實作,是以對于熟悉java 的開發者來說不是很難

spark 提供了用戶端一個基本的實作,并提出了一個很好的插件架構,這對于開發者來說不能不說是一個福音。我強烈建議基于插件方式來實作你新增加的功能,而不是去改它的源代碼,這樣有利于你項目架構,把原始項目的影響降到最低,文章以後的部分也是基于這種插件體系進行開發的

openfire 是基于xmpp 協定的im 的伺服器端的一個實作,雖然當兩個使用者連接配接後,可以通過點對點的方式來發送消息,但是使用者還是需要連接配接到伺服器來擷取一些連接配接資訊和通信資訊的,是以伺服器端是必須要實作的。openfire 也提供了一些基本功能,但真的很基本的!慶幸的是,它也提供插件的擴充,像spark 一樣,我同樣強烈建議使用插件擴充的方式來增加新的功能,而不是修改人家的源代碼。

smack 是一個xmpp 協定的java 實作,提供一套可擴充的api,不過有些時候,你還是不得不使用自己定制發送的xml 檔案内容的方式來實作自己的功能

下圖展示了三者之間的關系:

即時通訊軟體openfire+spark+smack

從圖上可以了解到,client 端和server端都可以通過插件的方式來進行擴充,smack是二者傳遞資料的媒介。

開發你自己的xmpp im 續 - spark 插件開發 - [j2ee]

繼續3月18日介紹基于xmpp im開發的那篇blog,今天主要總結一下如何基于spark 的插件架構來新增用戶端的功能,這裡列舉出一個擷取伺服器端群組資訊的實際例子,實作後的效果如下圖所示:

即時通訊軟體openfire+spark+smack

spark 是一個基于xmpp 協定,用java 實作的im 用戶端。它提供了一些api,可以采用插件機制進行擴充,上圖中,“部門”部分就是使用插件機制擴充出來的新功能。要想實作你的擴充,首先要了解 spark api的架構,其中最關鍵的是要了解它的工廠類,這些工廠類可以獲得spark 提供的諸如xmppconnection、chatcontainer 等執行個體,進而你可以實作擷取伺服器的資訊,與另外的client

通信等功能。最核心的類是sparkmanager,這個類是一系列工廠類的工廠類(呵呵,還真拗口)。它的getchatmanager()、getsessionmanager ()、getmainwindow() 、getconnection() 等方法分别可以獲得聊天管理器、會話管理器、主視窗、與伺服器的連接配接等等非常有用的執行個體。基本上可以說sparkmanager 是你與spark 打交道的銜接口。其實,每一個manager 都使用了單例模式,你也可以不通過sparkmanager 來擷取它們,但筆者建議你從單一的入口着手,這樣有利于代碼的開發和維護。

接下來描述一下插件的開發流程:

1、建立插件配置檔案 plugin.xml

2、實作你自己的plugin 類的實作(如果你需要實作自己規定格式的xml 發送、接收和處理,那麼你需要在這裡注冊你的iqprovider,關于iqprovider 你可以查詢smack api,簡單的來講是處理你自定義的iq 處理器。)

3、打包你的插件(spark 有自己的打包機制,我研究了半天才發現其中的玄機,後面介紹)

4、部署你的插件(其實3、4兩步可以糅合在一起,當然要利用ant 啦)

好滴,下面結合一個實際的例子講述上面的四個步驟

1、plugin.xml

<plugin>

    <name>enterprise im client</name>

    <version>1.0</version>

    <author>phoenix</author>

    <homepage>http://phoenixtoday.blogbus.com</homepage>

    <email>[email protected]</email>

    <description>enterprise client plug-in</description>

    <!-- 關鍵是這裡,這裡要定義你的plugin 類 -->

    <class>com.im.plugin.implugin</class>

    <!-- 這裡定義你使用的spark 最低版本 -->

    <minsparkversion>2.5.0</minsparkversion>

    <os>windows</os>

</plugin>

這是一個 plugin.xml 檔案的内容,插件體系會自動調用你在此檔案中定義的plugin 類,進而完成你自己擴充的功能。最關鍵的部分我用紅色辨別出來了,要聲明你的插件擴充類,采用完整的命名空間方式(包括包名),其餘的部分結合我的注釋,大家應該都能了解,就不做詳細的描述了。要注意的是plugin.xml 檔案要放在項目的根目錄下,這是嚴格規定好的。

2、plugin 類的實作

你的類首先要實作spark 提供的plugin 接口,然後實作它的一些方法。其中最主要的是實作initialize() 發放,在這裡注冊你的的iqprovider

providermanager providermanager = providermanager.getinstance();

providermanager.addiqprovider("groups", "com:im:group", //1

                new grouptreeiqprovider());

system.out.println("注冊grouptree iq 提供者");

requestgrouptree();

上述的代碼,就在該類就是我實作的implugin.initialize() 方法中的一小段,大概的含義是,先擷取providermanager(這個貌似不能從sparkmanager 直接擷取),然後注冊一個grouptreeiqprovider(自己建立的)這是一個iqprovider 的具體實作,它用于解析像下面這樣的一個xml 檔案:

<?xml version="1.0" encoding="utf-8"?>

    <groups xmlns='com:im:group'>

        <group>

             <groupid>1</groupid>

             <name>西安交通大學</name>

             <upgroup>root</upgroup>

             <isleaf>0</isleaf>

             <description>xjtu</description>

             <user>

                 <usergroupid>1</usergroupid>

                 <username>phoenix_test</username>

                 <role>normal</role>

             </user>

        </group>

             <groupid>2</groupid>

             <name>電信學院</name>

             <upgroup>1</upgroup>

             <isleaf>1</isleaf>

             <description>xjtu info</description>

    </groups>

</iq>

可以看到,在注冊 iqprovider 的時候(代碼中标注的1部分),需要你提供名稱和命名空間,我的xml 檔案中的iq 下的第一個子節點是<groups> 是以我的名稱就寫“groups”,命名空間對應于groups 節點的xmlns(xml name space)是以是“com:im:group”,其實iqprovider 中最關鍵的方法是parseiq(xmlpullparser parser)

該方法就是解析xml,完成你的功能,并傳回一個相應的iq 執行個體(這裡可以把iq 看做一個回饋的model 類)。說到底實作基于xmpp 協定的im 就是解析xml 檔案,而這正是用戶端的iqprovider 和伺服器端的iqhandler(下一篇文章會涉及到)所做的事情。

3、打包你的插件

現在該有的功能都實作了,那麼就是打包了。這最好利用ant 來完成,因為每次你都要打包,要部署,如果純手動的話,那也太不靈活了,大大影響開發效率。

<project name="im" default="release" basedir=".">

    <property name="src.dir" value="src" />

    <property name="dest.dir" value="bin" />

    <property name="lib.dir" value="lib" />

    <property name="im.path"

        value="e:/workspace/europa/spark_new/doc/spark/target/build" />

    <target name="clean">

        <!-- 

            <delete dir="${dest.dir}" />

            <delete dir="${lib.dir}" />

        -->

    </target>

    <target name="init" depends="clean">

            <mkdir dir="${dest.dir}" />

            <mkdir dir="${lib.dir}" />

    <target name="build" depends="init">

        <!--

            <javac srcdir="${src.dir}" destdir="${dest.dir}" />

    <!-- 最重要的是這裡,打兩次包 -->

    <target name="jar" depends="build">

        <jar jarfile="${lib.dir}/eim.jar" basedir="${dest.dir}" />

        <jar jarfile="${im.path}/plugins/eim.jar">

            <fileset dir=".">

                <include name="lib/*.jar" />

            </fileset>

                <include name="plugin.xml" />

        </jar>

    <target name="release" depends="jar">

            <exec executable="cmd.exe"

            failonerror="true">

            <arg line="/c e:"/>

            <arg line="/c cd workspace/europa/spark_new/doc/spark/target/build/bin"/>

            <arg line="/c startup.bat"/>

            </exec>

</project>

這是我的這個項目的 build.xml 檔案中的内容。因為eclipse 其實幫我自動完成了編譯的任務,是以我也就省去了這寫編譯的步驟,最重要的是大家要看到“jar” 部分,spark 打包的神秘之處也就在此,打兩次包首先把你的項目打包到本項目lib 檔案夾下,比如說你的項目目錄是myplugin 那麼,你就将你的類打包到myplugin/lib 目錄下,然後再次的打包,将所有的lib 檔案夾下的内容打包起來,記得這次要包含plugin.xml。也就是說,最後spark

插件體系會讀取你的項目下的lib 檔案夾下的内容。這裡我也有個疑問,我本來想每次打包後自動執行bat 檔案,啟動插件,看看效果,為啥死都調用不了呢,那段代碼在最後面,注釋掉了,誰能幫我解決,我請他吃飯滴!

4、最後就是釋出了

其實我的釋出很簡單,就是将這個打包好的jar 檔案拷到spark 本身的plugins 目錄下,每次啟動spark 的時候,它會自動調用自定義的插件的。我這裡用ant 第二次jar 的時候,就自動拷貝過去了,這裡用的是絕對路徑,是以你不能直接拷貝就用滴呦(是不是很醜陋呀,這段ant 代碼)。

基本上用戶端的實作原理就是這樣的,隻是有些地方需要特别注意,還有就是應該利用像ant 這樣的工具大大簡化開發步驟,加快開發效率。還有就是,我建議你在開發自己的插件的時候,多利用mvc 模式,尤其是在iqprovider 解析後,生成的部分可以執行個體化model,然後你可以編寫自己的manager 進行這些model 的處理。多寫log,當然log4j 貌似不太起作用,那就system.out.println()

吧,哈哈!今天就寫到這裡啦,偶有點累啦。

開發你自己的xmpp im 續 - openfire 插件開發 - [j2ee]

繼續上一篇的内容,本篇文章介紹開發openfire 的插件

這篇文章拖了很久了,呵呵,真是千呼萬喚始出來呀。openfire 伺服器端是支援插件開發的,開發過程可能會涉及到資料庫的操作,本篇文章專注于openfire 插件的部分,對伺服器端涉及到資料庫的開發隻做簡單介紹。

openfire 是一個用java 實作的xmpp 伺服器,用戶端可以通過iq 的方式與其進行通信(其實就是xml),用戶端和伺服器之間的通信是依靠底層smack 庫提供的各種功能來完成的。其實利用插件方式來擴充openfire 伺服器端主要有兩種擴充方式,一種是對伺服器控制台頁面進行擴充(不是本文的主要内容),其實就是遵循openfire 頁面的布局方式,進行相應的頁面擴充和功能擴充;另一種是對通信功能進行擴充。本文主要針對後者進行具體的描述

本篇文章的結構如下:

1、建立plugin.xml(這是整個插件最關鍵的文檔)

2、建立伺服器插件執行個體(實作plugin 接口的一個類還有一批iqhandler)

3、打包插件(openfire 插件也有自己的打包方式)和部署插件

好滴,實刀實槍的來動手做吧

1、建立plugin.xml

初次開發openfire 和spark 插件的時候,很容易把二者搞混,千萬記得,這裡是openfire 的plugin.xml 不是第二篇文章說的那個啦!

    <!-- main plugin class  這裡是最重要滴-->

    <class>com.im.server.plugin.grouptreeplugin</class>

    <!-- plugin meta-data -->

    <name>grouptreeplugin</name>

    <description>this is the group plugin.</description>

    <date>14/03/2008</date>

    <url>http://localhost:9001/openfire/plugins.jsp</url>

    <minserverversion>3.4.1</minserverversion>

    <licensetype>gpl</licensetype>

    <!-- admin console entries -->

    <adminconsole>

        <!-- more on this below -->

    </adminconsole>

最重要的那一行我已經标記出來啦,就是你這個插件的初始化和垃圾清理類,例子中是在com.im.server.plugin 包中的grouptreeplugin 類,下文會對這個類進行較長的描述。其餘的都是描述資訊,隻要你提供了正确的描述資訊,一般都不會出錯。建議初次開發者,在寫完plugin.xml 檔案後,寫一個簡單的plugin 執行個體,并列印出一些資訊,如果重新啟動openfire 資訊成功顯示,恭喜你,你已經邁出一大步了!

2、實作plugin 類和iqhandler

plugin 類主要起到的作用是初始化和釋放資源,在初始化的過程中,最重要的的注冊一批iqhandler,iqhander 的作用有點類似于spark 中的iqprovider,其實就是解析xml 檔案之後,生成一些有用的執行個體,以供處理。下面分别給出一個plugin 類的執行個體和iqprovider 的執行個體

grouptreeplugin 類

/**

 * 伺服器端插件類

 *

 * @author phoenix

 * mar 14, 2008 11:03:11 am

 * version 0.1

 */

public class grouptreeplugin implements plugin

{

    private xmppserver server;

    /*

     * (non-javadoc)

     *

     * @see org.jivesoftware.openfire.container.plugin#destroyplugin()

     */

    public void destroyplugin()

    {

    }

     * @see org.jivesoftware.openfire.container.plugin#initializeplugin(org.jivesoftware.openfire.container.pluginmanager,

     *      java.io.file)

    public void initializeplugin(pluginmanager manager, file plugindirectory)

        pluginlog.trace("注冊群組樹iq處理器");

        server = xmppserver.getinstance();

        server.getiqrouter().addhandler(new grouptreeiqhander()); //1

        server.getiqrouter().addhandler(new userinfoiqhandler());

        server.getiqrouter().addhandler(new deluseriqhandler());

        server.getiqrouter().addhandler(new createuseriqhandler());

        server.getiqrouter().addhandler(new addgroupuseriqhandler());

        server.getiqrouter().addhandler(new setroleiqhandler());

}

上例所示,在初始化中先找到iqrouter,然後通過iqrouter 注冊一批iqhandler,這些iqhander 會自動監聽相應命名空間的iq,然後進行處理;由于這個plugin 不需要做資源釋放的工作,是以在destroyplugin() 方法中沒有任何内容。具體的iqhander 類如下

grouptreeiqhander

 * 處理用戶端發來的iq,并回送結果iq

 * mar 14, 2008 4:55:33 pm

public class grouptreeiqhander extends iqhandler

    private static final string module_name = "group tree handler";

    private static final string name_space = "com:im:group";

    private iqhandlerinfo info;

    public grouptreeiqhander()

        super(module_name);

        info = new iqhandlerinfo("gruops", name_space);

     * @see org.jivesoftware.openfire.handler.iqhandler#getinfo()

    @override

    public iqhandlerinfo getinfo()

        return info;

     * @see org.jivesoftware.openfire.handler.iqhandler#handleiq(org.xmpp.packet.iq)

    public iq handleiq(iq packet) throws unauthorizedexception

        iq reply = iq.createresultiq(packet);

        element groups = packet.getchildelement();//1

        if (!iq.type.get.equals(packet.gettype()))

        {

            system.out.println("非法的請求類型");

            reply.setchildelement(groups.createcopy());

            reply.seterror(packeterror.condition.bad_request);

            return reply;

        }

        string username = stringutils.substringbefore(packet.getfrom().tostring(),"@");

        groupmanager.getinstance().initelement(groups,username);

        reply.setchildelement(groups.createcopy());//2

        system.out.println("傳回的最終xml" + reply.toxml());

        return reply;

可以看到主要有兩個方法,一個是getinfo() 這個方法的目的是提供要解析的命名空間,在本例中,這個iqhandler 對每個命名空間為"com:im:group" 的執行個體進行處理;還有一個最重要的方法:handleiq() 該方法對包含指定命名空間的xml 進行解析,然後傳回一個解析好的iq。其實我認為,這個iqhandler 和iq 的關系就是controller 和model 的關系(如果你了解mvc

的話,那麼你一定知道我再說什麼),隻不過這裡并沒有指定什麼view,你完全可以把iq 當成model 類進行了解。在這裡,我用了groupmanager 進行了xml 的處理,因為我傳回的iq 内容中要從資料庫讀取所有群組資訊,是以轉交給groupmanager 進行處理,你完全可以在這個方法中進行具體的xml 處理,在這裡,解析和建立新的xml 主要用到的是jdom(如果你對java 解析xml 有所了解,那真的太好了!)。程式//1 處主要是擷取建立傳回的iq,并擷取原來iq 的子元素(用于建立我們傳回的iq);程式//2

處很關鍵,如果你不調用createcopy 方法,程式會出錯(程式會死鎖還是什麼,忘記咧,不好以西)。

這就是程式的主體部分,我在這裡有一個建議,能不用openfire 原始的程式函數,就不要用它們。我的提取資料庫方式都是自己寫的bean,這樣有利于你自己對程式的掌控,其實更有利于快速開發(這世道不是啥都講究靈活麼,哇哈哈)

3、打包插件

打包依然遵循二次打包的原則(如果你不了解啥叫要二次打包,請看上一篇)

這是我的ant 檔案,由于eclipse 幫我做了build 等很多工作,實際我的ant 工作就是在打包,并放入插件目錄下的plugin 檔案夾下

    <property name="openfire.path"

        value="e:/workspace/europa/openfire_src/target/openfire" />

    <property name="classes.dir" value="classes" />

    <target name="jar">

        <jar jarfile="${lib.dir}/grouptreeplugin.jar" basedir="${classes.dir}" >

                <include name="*.jar"/>

        <jar jarfile="${openfire.path}/plugins/grouptreeplugin.jar">

                <include name="logo_small.gif" />

                <include name="logo_large.gif" />

                <include name="readme.html" />

                <include name="changelog.html" />

                <include name="build.xml" />

好了,至此xmpp+spark+openfire 的插件開發三部曲徹底結束了,希望你們對這個開發流程有了系統的了解。