【XMPP】Smack原始碼之初步認識

Leo.cheng發表於2014-02-16

Smack 概述

  Smack是一個用於和XMPP伺服器通訊的類庫,由此可以實現即時通訊和聊天。

Smack主要優勢

  • 非常簡單易用,並且有十分強大的 API。只需三行程式碼就可以向使用者發關文字訊息: 
    XMPPConnection connection = new XMPPConnection("jabber.org");
    connection.login("mtucker", "password");
    connection.createChat("jsmith@jivesoftware.com").sendMessage("Howdy!");
    View Code
  • 不像其它類庫那樣強制您進行包級別的編碼。Smack提供智慧的更高階的構造,像 ChatGroupChat類,讓您進行高效的程式設計。
  • 不需用您熟悉XMPP XML格式,即使您熟悉 XML.
  • 提供簡單的設計以進行通訊,Smack允許您在每個訊息中設定任意數量的屬性,包括java物件。
  • Apache許可下的開源類庫,這意味著您可以將Smack整合進您的商業或非商業的應用中。 

必要條件

  Smack的唯一必要條件是JDK 1.2 或更高版本。smack.jar檔案已包含一個XML解析器,不需要其它第三方類庫。

建立連線

  XMPPConnection類用來建立到XMPP伺服器的連線。要建立SSL連線,要使用SSLXMPPConnection類。

  下面是建立連線的例子:

// 建立一個到jabber.org伺服器的連線。
XMPPConnection conn1 = new XMPPConnection("jabber.org");

// 通過一個特殊的埠建立一個到jabber.org伺服器的連線。
XMPPConnection conn2 = new XMPPConnection("jabber.org", 5222);

// 建立一個到jabber.org伺服器的SSL連線。
XMPPConnection connection = new SSLXMPPConnection("jabber.org"); 
View Code

  一旦您建立了一個連線,您必須通過方法XMPPConnection.login(String username, String password)使用使用者名稱和密碼登陸

  如果登陸成功,您可以通過建立新的Chat或GroupChat物件和其它使用者聊天。  

操作Roster 

  Roster能夠讓您跟蹤其它使用者的有效性(存在)

  您可以通過使用像“朋友”和“同事”這樣的組來組織使用者,這樣您可以發現每個使用者是否線上。

  使用XMPPConnection.getRoster()這個方法得到Roster。

  通過Roster類您可以找到所有Roster登陸、他們所屬的組以及每個登陸當前的存在狀態。  

讀寫Packet 

  從客戶端以XML格式傳送到XMPP伺服器的每個訊息被稱為一個“packet”。

  org.jivesoftware.smack.packet包中包含了一些類,這些類封裝了XMPP所允許的三個不同的基本packet型別(message, presence, 和 IQ)。

  像Chat和GroupChat這樣的類提供了更高類別的構造能夠自動地建立和傳送packet,但是您也可以直接建立和傳送packet。

  下面是一個通過改變您的presence來讓別人知道您已無效,已經"out fishing"了: 

// 建立一個新的presence. 傳入false以指示我們已經無效了
Presence presence = new Presence(Presence.Type.UNAVAILABLE);
presence.setStatus("Gone fishing");
// 傳送packet (假設已經有了一個名為"con"的XMPPConnection例項).
con.sendPacket(presence);
View Code 

  Smack提供兩種方法讀取收到的packet:

  1. PacketListener[packet監聽器]PacketCollector[packet收集器]。
  2. 二者都是使用PacketFilter例項來決定哪個packet應該被處理。
  3. packet監聽器用於事件樣式的程式設計,而packet收集器有一個可以做輪詢和阻塞操作的packet的結果佇列。
  4. 所以,當您想對一個有可能隨時到來的packet採取一些操作時,使用packet監聽器;
  5. 而當您想等待一個特別的packet到來時,使用packet收集器。
  6. 可以使用XMPPConnection例項建立packet收集器和監聽器。  

使用Chat 和 GroupChat傳送訊息

  往復地傳送訊息處於即時通訊的核心地位。

  兩個類輔助傳送和接收訊息: 

    • org.jivesoftware.smack.Chat --用於在兩個人之間傳送訊息。
    • org.jivesoftware.smack.GroupChat --用於加入聊天室在多個人之間傳送訊息。

  ChatGroupCha類都是使用 org.jivesoftware.smack.packet.Message packet類來傳送訊息。

  在某種特定情況下,您可能不願意使用高階的Chat和GroupChat類而直接傳送和監聽訊息。 

Chat 

  一個chat在兩個使用者之間建立一個訊息執行緒(通過執行緒ID)。

  下面這段程式碼演示了怎樣和使用者建立一個新的Chat並向他們傳送一條文字訊息:

// 假設我們已經建立了一個名為"connection"的XMPPConnection。
Chat newChat = connection.createChat("jsmith@jivesoftware.com");
newChat.sendMessage("Howdy!");
View Code

  Chat.sendMessage(String)方法可以方便地建立一個Message物件,用字串引數設定訊息正文,然後傳送訊息。

  在一定情況下您可能希望在傳送訊息前設定額外的值,使用Chat.createMessage()Chat.sendMessage(Message)方法,如下面程式碼片段所示:

// 假設我們已經建立了一個名為"connection"的XMPPConnection。
Chat newChat = connection.createChat("jsmith@jivesoftware.com");
Message newMessage = newChat.createMessage();
newMessage.setBody("Howdy!");
message.setProperty("favoriteColor", "red");
newChat.sendMessage(newMessage);
View Code

  Chat物件能夠讓您很容易監聽其它聊天參與者的回覆。

  下面這段程式碼演示的功能類似鸚鵡學舌--它將回復對方輸入的一切訊息。 

// 假設我們已經建立了一個名為"connection"的XMPPConnection。
Chat newChat = connection.createChat("jsmith@jivesoftware.com");
newMessage.setBody("Hi, I'm an annoying parrot-bot! Type something back to me.");
while (true) {
    // 等待使用者傳送給我們的下一條訊息。
    Message message = newChat.nextMessage();
    // 將對方傳送過來的訊息原樣傳送給他。
    newChat.sendMessage(message.getBody());
}
View Code

  以上這段程式碼使用了這個Chat.nextMessage() 方法得到下一條訊息,它將等待不確定何時到來的另一條訊息。

  當然也有其它的方法用於等待特定時間段到來的新訊息,或者您可以新增一個監聽器,它將在每次有訊息到來時通知您。  

GroupChat 

  通過GroupChat連線到伺服器上的聊天室,您可以在一群人中傳送和接收訊息。但在您傳送或接收訊息之前,您必須用一個暱稱加入聊天室。

  下面這段程式碼演示了連線到一個聊天室併傳送一條訊息:

// 假設我們已經建立了一個名為"connection"的XMPPConnection。
GroupChat newGroupChat = connection.createGroupChat("test@jivesoftware.com");
// 用暱稱"jsmith"加入這處群。
newGroupChat.join("jsmith");
// 向聊天室中的其它人傳送一條訊息。
newGroupChat.sendMessage("Howdy!");
View Code

  通常,在群中傳送和接收訊息和在Chat類中非常相似。

  同時還提供了用於得到聊天室中其它人的列表的方法。 


處理收到的Packet

  Smack提供靈活的框架來通過兩種構造處理收到的 packet:

    • org.jivesoftware.smack.PacketCollector —— 一個讓您同步等待新packet的類。
    • org.jivesoftware.smack.PacketListener —— 一個非同步通知您引入的packet的介面。  

  packet監聽器用於事件樣式的程式設計,而packet收集器有一個可以做輪詢和阻塞操作的packet的結果佇列。

  所以,當您想對一個有可能隨時到來的packet採取一些操作時,使用packet監聽器;而當您想等待一個特別的packet到來時,使用packet收集器。

  您可以使用XMPPConnection例項建立packet收集器和監聽器。

  org.jivesoftware.smack.filter.PacketFilter 介面決定哪個特別的將會被傳遞到PacketCollectorPacketListener。

  org.jivesoftware.smack.filter package包中有許多預定義的過濾器。

  下面的程式碼片段演示註冊了一個packet收集器和一個packet 監聽器:

// 建立一個packet過濾器來監聽來自一個特定使用者的新的訊息
//我們可以使用一個AndFilter來結合其它兩個過濾器。 
PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class), 
        new FromContainsFilter("mary@jivesoftware.com"));
// 假設我們已經建立了一個名為"connection"的XMPPConnection。

// 首先,用我們建立的過濾器註冊一個packet收集器。
PacketCollector myCollector = connection.createPacketCollector(filter);
// 通常,您應該用收集器來些什麼,像等待新的packet。

// 接下來,建立一個packet監聽器。我們可以簡便地使用匿名內部類。
PacketListener myListener = new PacketListener() {
        public void processPacket(Packet packet) {
            // 在這裡用收到的packet做些什麼。
        }
    };
// 註冊這個監聽器。
connection.addPacketListener(myListener, filter);
View Code

標準Packet過濾器

  Smack包括豐富的packet 過濾器集,當然您可以通過實現PacketFilter介面建立自己的過濾器。

   預設的過濾器集包括:

    • PacketTypeFilter ——特定類的packet過濾器。
    • PacketIDFilter ——含有特定packet ID的packet過濾器。
    • ThreadFilter ——含有特定執行緒ID的訊息packet過濾器。
    • ToContainsFilter ——傳送到特定地址的packet過濾器。
    • FromContainsFilter ——來自特定地址的packet過濾器。
    • PacketExtensionFilter ——含有特定packet擴充的packet過濾器 filters for s that have a particular  extension.
    • AndFilter ——實現兩個過濾器的邏輯“與”操作。
    • OrFilter —— 實現兩個過濾器的邏輯“或”操作。
    • NotFilter ——實現一個過濾器的邏輯“非”操作。

Packet屬性

  Smack提供一個有效的機制,可以向packet附加任意屬性。

  每個屬性有一個String名字,這是一個java簡單型別值(int, long, float, double, boolean)and a value that is a Java primitive () 或者任何序列化物件(java物件可序列化當它實現了Serializable介面)。 

使用API

  所有主要物件支援屬性,如Message物件。

  下面的程式碼顯示如何設定屬性: 

Message message = chat.createMessage();
// 新增一個Color物件作為屬性。
message.setProperty("favoriteColor", new Color(0, 0, 255));
// 新增一個int作為屬性。
message.setProperty("favoriteNumber", 4);
chat.sendMessage(message);
View Code

  使用如下程式碼獲得這些屬性: 

Message message = chat.nextMessage();
// 獲得一個Color物件屬性。
Color favoriteColor = (Color)message.getProperty("favoriteColor");
// 獲得一個intg屬性,注意屬性作為物件返回,我們必須把值轉換為Integer,然後轉換為int。
int favoriteNumber = ((Integer)message.getProperty("favoriteNumber")).intValue();
View Code

物件作為屬性 

  使用物件作為屬性值是一個非常強大和容易的交換資料的方式。

  然而,應該牢記如下: 

    • Packet extension有更多標準方法新增額外資料到XMPP節。在某些情況下使用屬性可能更方便,由於Smack自身會做處理XML的工作。
    • 當你將Java物件作為屬性傳送時,只有執行著Java的客戶端能夠解釋資料。所以,應該考慮將資料轉換為一系列簡單型別的值來代替Java物件
    • 作為屬性值傳送的物件必須實現Serialiable。另外,傳送端和接收端都必須有同種的類,否則當系列化物件時會發生序列化異常。
    • 序列化的物件可能會很大,這將使用更多的頻寬和伺服器資源。 

XML格式 

  當前用於傳送屬性資料XML格式還不規範,所以很可能難以被不使用Smack的客戶端識別。

  XML猶如下面所示(附清晰的註釋): 

<!--某塊中的所有屬性。 --> 
<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties">
    <!-- 首選,一個名為"prop1"的integer型值。--> 
    <property>
        <name>prop1</name>
        <value type="integer">123</value>
    <property>
    <!-- 其次,一個序列化的Java物件,然後從二進位制資料轉換到base-64編碼的文字。 -->  
    <property>
        <name>blah2</name>
        <value type="java-object">adf612fna9nab</value>
    <property>
</properties> 
View Code

  前支援的型別有:integer, long, float, double, boolean, string, 和java物件。 


roster和presence

  roster能讓您跟蹤其它使用者的有效性(存在)。您可以通過使用像“朋友”和“同事”這樣的組來組織使用者。  

  其它IM系統如朋友列表,聯絡列表引用roster。

  一個roster例項通過XMPPConnection.getRoster()方法獲得,但僅當成功登陸伺服器之後對可用。

名薄登陸

  在roster中每個使用者用一個RosterEntry表示,它包括:

    • 一個XMPP地址(例如 jsmith@example.com)。
    • 一個您分配給使用者的名字(例如 "Joe")。
    • 登陸所屬的roster組列表。如果roster登陸不屬於任何組,它將被稱為“unfiled entry”。 

  下面的程式碼片段列印roster中的所有登陸:

Roster roster = con.getRoster();
for (Iterator i=roster.getEntries(); i.hasNext(); ) {
    System.out.println(i.next());
}
View Code

  也可能用方法獲得單個登陸,未定義登陸列表,或者獲得一個或所有roster組。

presence

  roster中的每個登陸有presence與之關聯。

  Roster.getPresence(String user)方法可以返回一個使用者Presence的物件,如果使用者不線上或您沒有預訂使用者的presence將會返回null。

  注意:一般而言,presence預訂一般受使用者是否在roster中的約束,但這並不適應所有情況。

  一個使用者可以有線上或離線兩種presence。

  當使用者線上時,他們的可能包含外延資訊,如他們正在做什麼,他們是否願意被打擾等等。參考Presence類以獲得更多細節資訊。

監聽roster和presence的變化

  roster類的典型應用就是顯示組的樹型檢視和含有當前presence值的登陸。

  presence資訊很可能經常變化,roster登陸也可能經常改變或被刪除。

  為了監聽roster和presence資料的變化,應該使用RosterListener

  下面的程式碼片段註冊了一個roster的RosterListener,它能夠在標準輸出中列印任何presence的變化。

  一個標準的客戶端可以使用類似的程式碼用變化的資訊來更新roster使用者介面。  

final Roster roster = con.getRoster();
roster.addRosterListener(new RosterListener() {
    public void rosterModified() {
        // 這個例子中忽略這個事件。
    }

    public void presenceChanged(String user) {
        // 如果presence無效,將會列印"null",
        // 這對本例來說很不錯。
        System.out.println("Presence changed: " + roster.getPresence(user));
    }
});
View Code

向roster中新增登陸 

  roster和presence使用一種基於許可的模式,使用者只有在被許可的情況下才能被新增到別人的roster中。

  這樣可以保護使用者的隱私因為只有經核准的其它使用者才能檢視他們的 presence資訊。

  因此,只有當其它使用者接受您的請求時您才能新增新的roster登陸。

  如果一個使用者請求presence預訂,因此他們可以把您新增到他們的roster中,您必須接受或拒絕該請求。

  Smack通過以下三種方式中的一種處理presence預訂請求: 

    • 自動接受所有presence預訂請求。
    • 自動拒絕所有presence預訂請求。
    • 手動處理presence預訂請求。

  通過Roster.setSubscriptionMode(int subscriptionMode)方法設定對請求的處理方式。

  簡易客戶端通常使用一種自動方式處理預訂請求,而複雜客戶端應該手動處理方式,請終端使用者接受或拒絕請求。

  如果使用手動方式,應該註冊一個PacketListener以監聽Presence.Type.SUBSCRIBE型別的Presence packet。


Privacy

Privacy是什麼

  Privacy是使用者阻擋其它個別使用者的通訊的方法。在XMPP中它通過操作隱私列表完成。
  通過下面的用例伺服器端隱私列表能夠成功完成:  

  • 重新獲得某人的隱私列表。
  • 新增,刪除,並編輯某人的隱私列表。
  • 設定,更改,或放棄活動列表。
  • 設定,更改,或放棄預設列表(也就是預設活動的列表)。
  • 基於JID,組,或簽名型別(或全部)允許或阻擋訊息。
  • 基於JID,組,或簽名型別(或全部)允許或阻擋向內的出席通知。
  • 基於JID,組,或簽名型別(或全部)允許或阻擋向外的出席通知。
  • 基於JID,組,或簽名型別(或全部)允許或阻擋IQ節。
  • 基於JID,組,或簽名型別(或全部)允許或阻所有通訊。

怎麼使用它

  API實現有三個主公共類: 

  • PrivacyListManager: 這是重新獲得並處理伺服器隱私列表的主API類。
  • PrivacyList: 代表一個隱私列表,有一個名字,一組隱私專案。例如,可見或不可見列表。
  • PrivacyItem:阻擋或允許隱私的某個方面。例如,允許我的的朋友看到我們的出席狀態。 

  1.正確從頭開始,客戶端可以獲得他的/她的儲存在伺服器上的隱私列表:

// 為當前連線建立一個隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
// 重新獲得伺服器隱私列表。
    PrivacyList[] lists = privacyManager.getPrivacyLists();
View Code

   正在客戶端能夠顯示伺服器的每個PrivacyItem和每個列表是否是活動的,預設或沒有。客戶端是一個隱私變化的監聽器。

  2.要向伺服器新增一個新列表,客戶端可以像這樣執行:

// 設定列表的名稱
    String listName = "newList";

    // 建立PrivacyItem的列表,PrivacyItem將會允許或拒絕某些隱私方面。
    String user = "tybalt@example.com";
    String groupName = "enemies";
    ArrayList privacyItems = new ArrayList();

    PrivacyItem item = new PrivacyItem(PrivacyRule.JID, true, 1);
    item.setValue(user);
    privacyItems.add(item);

    item = new PrivacyItem(PrivacyRule.SUBSCRIPTION, true, 2);
    item.setValue(PrivacyRule.SUBSCRIPTION_BOTH);
    privacyItems.add(item);
         
    item = new PrivacyItem(PrivacyRule.GROUP, false, 3);
    item.setValue(groupName);
    item.setFilterMessage(true);
    privacyItems.add(item);

    // 獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 建立新列表。
    privacyManager.createPrivacyList(listName, Arrays.asList(privacyItems));
View Code

  3.修改一個已存的列表,客戶端程式碼可能像這樣: 

// 設定列表名稱
    String listName = "existingList";
    //獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 向伺服器傳送新列表。
    privacyManager.updatePrivacyList(listName, items);
View Code

  注意items在例2中定義並且必須包含列表中的所有元素(not the "delta")。 

  4.刪除一個已存在的列表,客戶端可以像這樣執行: 

// 設定列表名稱
    String listName = "existingList";
    // 獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 刪除列表。
    privacyManager.deletePrivacyList(listName);
View Code

   5.放棄活動列表的使用,客戶端可以像這樣執行:

// 獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 放棄活動列表的使用。
    privacyManager.declineActiveList();
View Code

  6.放棄預設列表的使用,客戶端可以像這樣執行: 

// 獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 放棄預設列表的使用。
    privacyManager.declineDefaultList();
View Code

監聽Privacy變化

  為了處理隱私變化,客戶端應該監聽管理器的更新。

  當一個列表更改時管理器通知每個已新增的監聽器。

  監聽必須實現PrivacyListListener介面。當隱私列表被修改時客戶端可能需要作出反應。

  PrivacyListManager讓您新增監聽器,它將在列表被改變時得知通知。監聽器應該實現PrivacyListListener介面
  最重要的通知是updatedPrivacyList,它當隱私列表改變它的隱私專案時被執行。
  當執行如下程式碼監聽器能得到通知:  

// 獲得當前連線的隱私管理器。
    PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(myConnection);
    // 為了得到通知新增監聽器(this)
    privacyManager.addListener(this);
View Code

 

相關文章