【XMPP】Smack源碼之初步認識

Smack 概述

  Smack是一個用於和XMPP服務器通訊的類庫,由此能夠實現即時通信和聊天。java

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可以讓您跟蹤其它用戶的有效性(存在)ide

  您能夠經過使用像「朋友」和「同事」這樣的組來組織用戶,這樣您能夠發現每一個用戶是否在線。this

  使用XMPPConnection.getRoster()這個方法獲得Roster。編碼

  經過Roster類您能夠找到全部Roster登錄、他們所屬的組以及每一個登錄當前的存在狀態。  spa

讀寫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
相關文章
相關標籤/搜索