原文地址:http://blog.csdn.net/xunshu/archive/2008/03/27/2223817.aspx
Smack是一個爲使用XMPP服務器聊天和發送即時消息交流而提供的庫。
Smack的主要優勢:
l 使用簡單且擁有強大的API。向用戶發送一條文本消息只需用一下三行代碼即可完成
XMPPConnection connection = new XMPPConnection("jabber.org"); connection.login("mtucker", "password"); connection.createChat("[email protected]").sendMessage("Howdy!; |
l 不像其它庫那樣,強制你在信息報級(packet level)編碼。Smack提供智能的、更高級別的結構,例如:Chat和GroupChat類,這寫能讓你的程序效率更高。
l 你不需要熟悉XMPP XML格式,甚至不熟XML。
l 提供簡單的機器到機器的通訊。Smack允許你對每一條消息設置任何數字的屬性,包括Java對象的屬性。
l Apache許可的開放源碼,你可將其用於商業的和非商業的應用。
關於XMPP
XMPP (eXtensible Messaging and Presence Protocol)是一個開放的,
如何使用本文檔
本文檔假定你已經熟悉XMPP即時消息的主要特徵。我們推薦你在閱讀該文檔時打開Javadoc API作爲參考。
開始Smack
本文檔將向你介紹Smack API,並大概介紹幾個重要的類和概念。
必備的條件
你只需要有JDK 1.2或之後的版本1和已經內嵌在smack..jar文件中的XML分析器,不需要第三部分庫。
1JDK 1.2 and 1.3的用戶若想使用SSL連接必須在他的類路徑下有JSSE庫。
建立一個連接
XMPPConnection類是爲XMPP服務器建立連接的類。若要創建SSL連接,需使用SSLXMPPConnection類,以下是創建連接的例子。
// Create a connection to the jabber.org server. XMPPConnection conn1 = new XMPPConnection("jabber.org");
// Create a connection to the jabber.org server on a specific port. XMPPConnection conn2 = new XMPPConnection("jabber.org", 5222);
// Create an SSL connection to jabber.org. XMPPConnection connection = new SSLXMPPConnection("jabber.org"); |
如果創建了一個連接,你應該使用XMPPConnection.login(String username, String password)方法(參數爲用戶名和密碼)進行登陸。一旦登陸成功,你就可以通過創建一個新的Chat 或GroupChat對象與其它用戶聊天。
使用花名冊(Working with the Roster)
花名冊讓你很清楚的知道其它可用的用戶。用戶可以被分成像“朋友”、“合作者”這樣的組,從而知道其它的用戶在線還是離線。
可以使用XMPPConnection.getRoster()方法檢索花名冊。你可以用花名冊(roster)類查找花名冊的所有條目,它們所屬的組以及每個條目當前呈現的狀態。
讀、寫信息包(Reading and Writing Packets)
從客戶端發送到XMPP的每一條消息稱爲一個信息包,並作爲XML發送。The org.jivesoftware.smack.packet包含封裝了三個XMPP允許的、不同的基本包類型(message, presence, and IQ)的類。像Chat和GroupChat這樣的類提供更高級別的結構,它可以自動的創建和發送信息包,當然你也可以直接創建和發送信息包。以下代碼是一個將你的當前狀態改爲“隱身“,從而不被別人看到的例子:
// Create a new presence. Pass in false to indicate we're unavailable. Presence presence = new Presence(Presence.Type.UNAVAILABLE); presence.setStatus("Gone fishing"); // Send the packet (assume we have a XMPPConnection instance called "con"). con.sendPacket(presence); |
Smack提供以下兩種方法閱讀收到的信息包:PacketListener和PacketCollector。它們都使用PacketFilter的實例來決定應該處理哪個信息包。信息包監聽器(packet listener)用於事件類型的設計,而信息包收集器(packet collector)有一個信息包的結果隊列,你可以對其實施polling和blocking操作。所以,信息包監聽器在你收到任何一個信息包,且你想對其進行操作時是有用的,而信息包收集器在你想等待某個特殊的信息包時是有用的。信息包收集器和監聽器可以通過XMPPConnection的實例來創建。
Messaging Basics
Messaging using Chat and GroupChat
互相發送消息是即時通訊的核心,以下是兩個在收發消息是用的類:
- org.jivesoftware.smack.Chat – 用於兩個人之間發送消息
- org.jivesoftware.smack.GroupChat –用於加入聊天室,很多人之間相互發送消息。
Chat和GroupChat類都用org.jivesoftware.smack.packet .Message信息包類發送消息。在某些情況下,也許你希望繞過更高級別的Chat和GroupChat類直接發送和接受消息。
聊天(Chat)
聊天時在兩個用戶間創建了一個新的線程(使用一個線程ID)。以下程序片示例瞭如何如何與一個用戶進行開始聊天併發送一段文本消息:
// Assume we've created an XMPPConnection name "connection". Chat newChat = connection.createChat("[email protected]"); newChat.sendMessage("Howdy!");
|
Chat.sendMessage(String)方法可以很方便的創建一個消息對象,方法體使用字符串類型的參數,然後發送消息。如果想在發送消息前對消息設置額外的只,可以使用Chat.createMessage() and Chat.sendMessage(Message)方法,如下例所示:
// Assume we've created an XMPPConnection name "connection". Chat newChat = connection.createChat("[email protected]"); Message newMessage = newChat.createMessage(); newMessage.setBody("Howdy!"); message.setProperty("favoriteColor", "red"); newChat.sendMessage(newMessage);
|
使用Chat對象可以輕鬆的收聽其它聊天者的回覆。以下程序片是parrot-bot,它映射會其它用戶類型的所有事情:
// Assume we've created an XMPPConnection name "connection". Chat newChat = connection.createChat("[email protected]"); newMessage.setBody("Hi, I'm an annoying parrot-bot! Type something back to me."); while (true) { // Wait for the next message the user types to us. Message message = newChat.nextMessage(); // Send back the same text the other user sent us. newChat.sendMessage(message.getBody()); }
|
以上代碼使用Chat.nextMessage()方法獲得下一條消息,它必需一直等待直到收到下一條消息。也有其它的方法可以等待特定的時間來接受下一條消息,或者你也可以增加一個監聽器,它可以在每次收到消息時通知你。
羣聊(GroupChat)
羣聊在通過一個服務器連接到聊天室,你可以向一組人發送消息或接收他們的消息。在你能接收和發送消息前,你必須使用一個暱稱登陸到聊天室。以下程序段可以連接到一個聊天室併發送消息:
// Assume we've created an XMPPConnection name "connection". GroupChat newGroupChat = connection.createGroupChat("[email protected]"); // Join the group chat using the nickname "jsmith". newGroupChat.join("jsmith"); // Send a message to all the other people in the chat room. newGroupChat.sendMessage("Howdy!");
|
,羣聊時收發消息和私聊時工作原理大體一致。同樣,也有方法可以獲得聊天室裏其它用戶的列表。
Roster and Presence
花名冊讓你很清楚的知道其它可用的用戶。用戶可以被分成像“朋友”、“合作者”這樣的組。其它的即使通訊系統將花名冊作爲好友列表、聯繫列表等。
當你成功登陸服務器後,可以使用XMPPConnection.getRoster()獲得Roster類的實例。
花名冊條目(Roster Entries)
花名冊裏的每一個用戶都以一條花名冊條目的形式呈現,包括以下幾部分:
- 一個XMPP地址(例如:[email protected]).
- 分配給你的用戶名 (例如: "Joe").
- 該條目在花名冊中所屬組的列表。如果該條目不屬於任何一個組,將被稱爲“尚未分類的條目。
以下程序段可以打印出花名冊中的所有條目:
Roster roster = con.getRoster(); for (Iterator i=roster.getEntries(); i.hasNext(); ) { System.out.println(i.next()); }
|
也有獲得個人條目、尚未分類條目的列表、一個或者所有組的方法。
呈現(Presence)
花名冊中的每一個條目都有相關的呈現方式。Roster.getPresence(String user)方法將通過用戶的狀態或當用戶不在線或不同意將其在線狀態顯示出來時使用空對象(null)返回一個Presence對象。
注意:一般情況下,用戶是否同意顯示其狀態依賴於用戶所在的花名冊,但這不是在所有情況下都成立的。
用戶也有一個在線或離線的狀態,如果用戶在線,他們的顯示信息中將會有一些擴展的信息,例如他當前正在做什麼,是否希望被打擾等等,詳細內容可以參看Presence類。
Listening for Roster and Presence Changes
Roster類的典型用途是用樹狀形式顯示組和每一個條目以及它的當前狀態。如下圖所示是 Exodus XMPP客戶端的花名冊。
顯示的信息很可能會經常改變,也有可能是花名冊的條目被改變甚至被刪除。爲了監視花名冊的改變和顯示的信息,應該使用一個花名冊監聽器(RosterListener)。以下代碼使用Roster(它可以打印出花名冊中的任何變化)註冊了一個RosterListener。標準的客戶端應該使用相似的代碼更新花名冊的用戶信息(roster UI)以正確顯示變化的信息。
final Roster roster = con.getRoster(); roster.addRosterListener(new RosterListener() { public void rosterModified() { // Ignore event for this example. } public void presenceChanged(String user) { // If the presence is unavailable then "null" will be printed, // which is fine for this example. System.out.println("Presence changed: " + roster.getPresence(user)); } });
|
向花名冊中添加條目(Adding Entries to the Roster)
花名冊和顯示使用基於許可的模型,這要求用戶在加入別人的花名冊前必須得到允許。這樣,確保只有被允許的人纔可以看到自己所顯示的信息,從而保護了用戶的隱私。因此,在你想添加一個新的條目,且對方沒有接受你的請求前,該條目將處於等待狀態。
如果另一個用戶請求同意顯示,從而你他們可以將你加入他們的花名冊,你必須接受或拒絕請求。Smack通過以下三種方式之一操作同意顯示請求:
- 自動接受所有的同意顯示請求。
- 自動拒絕所有的同意顯示請求。
- 手動處理同意顯示請求。
可以使用Roster.setSubscriptionMode(int subscriptionMode)方法設置模式。簡單的客戶通常使用一個自動接受或拒絕同意顯示請求的模式,而用更多特徵的用戶應該使用手動處理同意顯示請求的模式,並讓終端用戶接受或拒絕每一個請求。如果使用手動模式,應該聲明一個信息包監聽器(PacketListener)來監聽有Presence.Type.SUBSCRIBE類型的顯示信息包。
處理收到的信息包(Processing Incoming Packets)
Smack提供一個使用以下兩個結構的靈活框架來處理收到的信息包:
- org.jivesoftware.smack.PacketCollector – 一個允許你同步的等待新的信息包的類
- org.jivesoftware.smack.PacketListener – 一個異步的通知你收到信息包的接口
信息包監聽器(packet listener)用於事件類型的設計,而信息包收集器(packet collector)有一個信息包的結果隊列,你可以對其實施polling和blocking操作。所以,信息包監聽器在你收到任何一個信息包,且你想對其進行操作時是有用的,而信息包收集器在你想等待某個特殊的信息包時是有用的。信息包收集器和監聽器可以通過XMPPConnection的實例來創建。
由org.jivesoftware.smack.filter.PacketFilter接口來決定哪個特殊的信息包將被轉交給信息包收集器(PacketCollector)或信息包監聽器(PacketListener)。可以在org.jivesoftware.smack.filter包中找到許多預先定義的過濾器。
以下代碼闡釋瞭如何註冊一個信息包收集器(packet collector)和信息包監聽器(packet listener):
// Create a packet filter to listen for new messages from a particular // user. We use an AndFilter to combine two other filters. PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter("[email protected]")); // Assume we've created an XMPPConnection name "connection". // First, register a packet collector using the filter we created. PacketCollector myCollector = connection.createPacketCollector(filter); // Normally, you'd do something with the collector, like wait for new packets. // Next, create a packet listener. We use an anonymous inner class for brevity. PacketListener myListener = new PacketListener() { public void processPacket(Packet packet) { // Do something with the incoming packet here. } }; // Register the listener. connection.addPacketListener(myListener, filter);
|
標準信息包過濾器(Standard Packet Filters)
Smack包含一套豐富的信息包過濾器,你也可以通過信息包過濾器接口(PacketFilter interface)編寫程序來創建自己的過濾器。缺省的過濾器集包括:
- PacketTypeFilter – 某個特殊的類類型的信息包過濾器
- PacketIDFilter – 擁有特殊的信息包ID(packet ID)的過濾器
- ThreadFilter – 擁有特殊線程ID(thread ID)的信息包的過濾器
- ToContainsFilter –發送到某個特殊地址的信息包的過濾器
- FromContainsFilter --發送到某個特殊地址的信息包的過濾器
- PacketExtensionFilter – 擁有特殊的信息包擴展的信息包的過濾器
- AndFilter –對兩個過濾器實施邏輯與操作的過濾器
- OrFilter --對兩個過濾器實施邏輯或操作的過濾器
- NotFilter --對一個過濾器實施邏輯非操作的過濾器
Provider Architecture: Packet Extensions and Custom IQ's
Smack提供的體系是堵塞自定義的XML信息包擴展和IQ包分析器的系統(The Smack provider architecture is a system for plugging in custom XML parsing of packet extensions and IQ packets)。標準的Smack擴展(Smack Extensions)是使用提供者的體系結構搭建的。存在以下兩種類型的提供者:
- IQProvider –將IQ請求( IQ requests)解析成Java對象(Java objects)
- PacketExtension – 將附屬在信息包上的XML子文檔解析成信息包擴展實例(PacketExtension instances)
IQProvider
默認情況下,Smack致知道如何處理只有類似以下幾個名字空間的子信息包的IQ信息包(IQ packets):
- jabber:iq:auth
- jabber:iq:roster
- jabber:iq:register
因爲許多IQ類型是XMPP及其擴展部分的一部分,所以提供一個可插入的IQ分析機制。IQ Providers被程序自動的註冊或通過創建在你的JAR 文件的META-INF目錄下創建一個mack.providers文件。該文件是一個包含一個或多個iqProvider條目(iqProvider entries)的XML文檔,如下例所示:
<?xml version="1.0"?> <smackProviders> <iqProvider> <elementName>query</elementName> <namespace>jabber:iq:time</namespace> <className>org.jivesoftware.smack.packet.Time</className> </iqProvider> </smackProviders>
|
每一個IQ provider都和一個元素名(element name)和名字空間( namespace)相聯繫。在上面的例子中,元素名是query,名字空間是abber:iq:time。如果有多重提供者條目(multiple provider entries)嘗試註冊並控制相同的名字空間,那麼從類路徑(classpath)載入的第一個條目將有優先權。
IQ provider類可以實現IQProvide接口,或者繼承IQ類。在前面的例子中,每一個IQProvider負責解析原始的XML流從而創建一個IQ實例。在下面的例子中,bean introspection將被用於嘗試自動使用在IQ packet XML中發現的值設置IQ實例的屬性。一個XMPP時間信息包如下所示:
<iq type='result' to='[email protected]' from='[email protected]' id='time_1'> <query xmlns='jabber:iq:time'> <utc>20020910T17:58:35</utc> <tz>MDT</tz> <display>Tue Sep 10 12:58:35 2002</display> </query> </iq>
|
爲了讓這個信息包自動的映射成上面的providers file中所列的時間對象(Time object),它必須有以下幾個方法:setUtc(String), setTz(String), 和 setDisplay(String)。自動檢查(introspection)的服務將試着自動的將字符串值轉化成a boolean, int, long, float, double,或 Class 類型。轉化成何種類型由IQ實例的需要來決定。
PacketExtensionProvider
信息包插件提供者(Packet extension providers)爲信息包提供一個可插入的系統,這些信息包是一個IQ, message和presence packets的自定義名字空間的子元素。每一個插件提供者(extension provider)使用一個元素名(element name)和名字空間(namespace)在smack.providers文件中註冊,如下例所示:
<?xml version="1.0"?> <smackProviders> <extensionProvider> <elementName>x</elementName> <namespace>jabber:iq:event</namespace> <className>org.jivesoftware.smack.packet.MessageEvent</className> </extensionProvider> </smackProviders>
|
如果有多重提供者條目(multiple provider entries)嘗試註冊並控制相同的名字空間,那麼從類路徑(classpath)載入的第一個條目將有優先權。
一旦在一個信息包中發現信息包插件,解析器將傳遞到正確的提供者。每一個提供者可以實現PacketExtensionProvider接口或者是一個標準的Java Bean。在前面的例子中,每一個插件提供者(extension provider)負責解析原始的XML流去構造一個實例。在下面的例子中,bean introspection將被用於嘗試自動使用在信息包插件子元素(packet extension sub-element)中的值設置類的屬性。
當一個插件提供者(extension provider)沒有用元素名(element name)和名字空間(namespace)對註冊是,Smack將存儲所有在缺省信息包插件(DefaultPacketExtension)對象中的最高級別元素(top-level elements),並匹配到信息包上。
信息包屬性(Packet Properties)
Smack提供簡單的機制來將任意的屬性附加到信息包上。每一個屬性有個字符串類型的名字和一個值,這個值或者是Java原始數據類型(int, long, float, double, boolean)的,或者是任何可序列化的對象(Serializable object)(當一個java對象實現了Serializable接口時,它就是可序列化的)。
使用API(Using the API)
所有主要對象都有屬性支持,例如消息對象(Message objects)。以下代碼闡釋瞭如何設置屬性:
Message message = chat.createMessage(); // Add a Color object as a property. message.setProperty("favoriteColor", new Color(0, 0, 255)); // Add an int as a property. message.setProperty("favoriteNumber", 4); chat.sendMessage(message);
|
獲得這些相同的屬性要用到以下的代碼:
Message message = chat.nextMessage(); // Get a Color object property. Color favoriteColor = (Color)message.getProperty("favoriteColor"); // Get an int property. Note that properties are always returned as // Objects, so we must cast the value to an Integer, then convert // it to an int. int favoriteNumber = ((Integer)message.getProperty("favoriteNumber")).intValue();
|
將對象作爲屬性(Objects as Properties)
將對象作爲屬性值是改變數據的一個非常有力和簡單的方法。但是,你應該記住以下幾點:
l 信息包插件(Packet extensions)是向XMPP增加額外數據的更權威的方式。使用屬性在某種情況下也許會比較方便,但是,Smack將會控制XML。
l 當你將Java對象(Java object)作爲屬性發送時,只有在客戶機運行的Java能夠解釋數據。所以,可以考慮使用一系列的原始值來傳遞數據。
l 作爲屬性值發送的對象必須實現序列化接口(Serialiable)。除此之外,發送者和接受者都必須由相同版本的類,否則在反序列化(de-serializing the object)對象時將發生序列化異常。
l 序列化的對象將會非常大,將會佔用很多的服務器資源和帶寬。
XML格式(XML Format)
當前的用於發送屬性的XML格式不是標準的,所以可能不會得到使用Smack的客戶的認可。XML如下所示(爲了更清晰添加了註釋):
<!-- All properties are in a x block. --> <properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties"> <!-- First, a property named "prop1" that's an integer. --> <property> <name>prop1</name> <value type="integer">123</value> <property> <!-- Next, a Java object that's been serialized and then converted from binary data to base-64 encoded text. --> <property> <name>blah2</name> <value type="java-object">adf612fna9nab</value> <property> </properties>
|
當前支持的數據類型有:integer, long, float, double, boolean, string, 和java-object。
使用Smack調試(Debugging with Smack)
Smack包含兩個內置的調試控制檯,他們允許你在服務器和客戶機建跟蹤XML的蹤跡。簡單的調試器(lite debugger)是smack.jar的一部分,加強的調試器(enhanced debugger)包含在(smackx-debug.jar)中。
可以用兩種不同的方法激活調試模式:
1. 在創建連接前加入以下一行代碼:
XMPPConnection.DEBUG_ENABLED = true;
2. 將Java的系統屬性smack.debugEnabled設置爲true。這一系統屬性可通過下一命令行設置:
java -Dsmack.debugEnabled=true SomeApp
在你的應用程序中,如果你想明確的禁用調試模式,包括使用命令行參數,則在打開新的連接前在你的應用程序中添加以下一行代碼:
XMPPConnection.DEBUG_ENABLED = false;
Smack使用一下的邏輯來決定使用哪個調製控制檯:
- 它將首先嚐試使用Java系統屬性smack.debuggerClass 所指定的調試類(debugger class)。如果你需要開發自己的調試器 I,可以實現SmackDebugger 接口然後使用下面的命令行設置系統屬性:
java -Dsmack.debuggerClass=my.company.com.MyDebugger SomeApp
- 如果第一步失敗了,Smack就會嘗試使用增強的調試器(enhanced debugger)。 文件 smackx-debug.jar 包含,因此你要把jar文件放到類路徑(classpath)下。如果空間確定你只是想要配置smack.jar文件,這種情況下增強的調試器(enhanced debugger)將不可用。
- 最後一種是前面兩種都失敗後使用簡單的調試器(ite debugger)。在你的內存很小的時候,簡單的調試器(ite debugger)是一個很好的選擇。
增強的調試器(enhanced debugger)
當調試模式可用時,將出現一個包含每一個創建的連接的標籤調試窗口,該窗口包含以下信息:
- 連接標籤(Connection tabs) -- 每一個標籤顯示連接相關的調試信息
- Smack信息標籤(Smack info tab) -- 顯示關於Smack的信息 (例如: Smack的版本(Smack version), 安裝的組件(installed components),等等)。
連接標籤包含以下信息:
- 所有的信息包(All Packets) -- 顯示由Smack解析的發送和收到的信息包的信息。
- 未經處理的發送信息包(Raw Sent Packets) -- 未經處理的XML traffic(raw XML traffic)由Smack生成併發送至服務器 。
- 未經處理的接收信息包(Raw Received Packets) --未經處理的XML traffic(raw XML traffic)由服務器發送給客戶機。
- Ad-hoc 消息(Ad-hoc message) -- 允許發送各種類型的ad-hoc信息包(ad-hoc packets)。
- 信息—顯示連接狀態和統計信息。
簡單的調試器(Lite Debugger )
當調試模式可用時,每創建一個連接將出現調試窗口,該窗口包含以下信息:
- 客戶端的流量(Client Traffic) (紅色的文本) --未經處理的XML traffic(raw XML traffic)由Smack生成併發送至服務器 。
- 服務器端的流量(Server Traffic)(藍色的文本) --未經處理的XML traffic(raw XML traffic)由服務器發送給客戶機。
- 解釋的信息包(Interpreted Packets)(綠色的文本) – 顯示來自服務器的由Smack解析的XML信息包(XML packets)
右擊任何面板會出現一個菜單,上面有一個選項可以將這些內容拷貝到系統的剪貼板或者清除面板中的內容。