天天看點

xmpp開發

2008-03-18

轉自http://phoenixtoday.blogbus.com/logs/17246727.html

最近沒在Blog 上露臉,為撒類?應師兄的請求,幫他研究一個XMPP IM 軟體的開發。反正最近也沒什麼大事,每天都想寫寫代碼練練手,就幫忙呗。研究了一通覺得還挺有趣,自己這幾天查國内外的資料,發現國内關于這方面間的軟體資料太少了,就想在這裡寫幾篇關于此類IM 軟體開發的文章。不過别看東西小,涉及的子產品可不少。

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

第一篇是關于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 檔案内容的方式來實作自己的功能

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

xmpp開發

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

嗯,今天就寫到這裡吧,畫圖花了好久的時間呀。下一篇介紹一下如何開發基于Spark 的插件程式,并總結一些我自己的一些心得,舉一個實際的例子,需要涉及到的一些XML 檔案的解析與生成,還有Spark、Smack 自己提供的API ,還有ant 的一些基本知識

開發你自己的XMPP IM 續 - Spark 插件開發 - [J2EE]

2008-03-29

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

xmpp開發

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"?>

<iq type='result' to='[email protected]' from='[email protected]' id='request_1'>

<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>

<group>

<groupId>2</groupId>

<name>電信學院</name>

<upGroup>1</upGroup>

<isLeaf>1</isLeaf>

<description>xjtu info</description>

</group>

</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 來完成,因為每次你都要打包,要部署,如果純手動的話,那也太不靈活了,大大影響開發效率。

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

<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>

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

<!--

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

-->

</target>

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

<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

public class GroupTreePlugin implements Plugin

{

private XMPPServer server;

public void destroyPlugin()

{

}

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

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);

}

@Override

public IQHandlerInfo getInfo()

{

return info;

}

@Override

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 檔案夾下

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

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

<property name="openfire.path"

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

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

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

<target name="jar">

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

<fileset dir=".">

<include name="*.jar"/>

</fileset>

</jar>

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

<fileset dir=".">

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

<include name="plugin.xml" />

<include name="logo_small.gif" />

<include name="logo_large.gif" />

<include name="readme.html" />

<include name="changelog.html" />

<include name="build.xml" />

</fileset>

</jar>

</target>

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

</target>

</project>

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