天天看點

XMPP之Smack 介紹XMPP簡介工作原理代碼抽取

XMPP簡介

原理

  • 概述

    XMPP(可擴充消息處理現場協定)是基于可擴充标記語言(XML)的協定,它用于即時消息(IM)以及線上現場探測。它在促進伺服器之間的準即時操作。這個協定可能最終允許網際網路使用者向網際網路上的其他任何人發送即時消息,即使其作業系統和浏覽器不同。

XMPP之Smack 介紹XMPP簡介工作原理代碼抽取

工作原理圖

XMPP協定網絡架構

XMPP是一個典型的C/S架構,而不是像大多數即時通訊軟體一樣,使用P2P用戶端到用戶端的架構,也就是說在大多數情況下,當兩個用戶端進行通訊時,他們的消息都是通過伺服器傳遞的(也有例外,例如在兩個用戶端傳輸檔案時).采用這種架構,主要是為了簡化用戶端,将大多數工作放在伺服器端進行,這樣,用戶端的工作就比較簡單,而且,當增加功能時,多數是在伺服器端進行.XMPP服務的架構結構如下圖所示.XMPP中定義了三個角色,XMPP用戶端,XMPP伺服器、網關.通信能夠在這三者的任意兩個之間雙向發生.伺服器同時承擔了用戶端資訊記錄、連接配接管理和資訊的路由功能.網關承擔着與異構即時通信系統的互聯互通,異構系統可以包括SMS(短信)、MSN、ICQ等.基本的網絡形式是單用戶端通過TCP/IP連接配接到單伺服器,然後在之上傳輸XML

主要特點

  • XMPP 協定是公開的,由JSF開源社群組織開發的。XMPP 協定并不屬于任何的機構和個人,而是屬于整個社群,這一點從根本上保證了其開放性。
  • XMPP 協定具有良好的擴充性。在XMPP 中,即時消息和到場資訊都是基于XML 的結構化資訊,這些資訊以XML 節(XML Stanza)的形式在通信實體間交換。XMPP 發揮了XML 結構化資料的通用傳輸層的作用,它将出席和上下文敏感資訊嵌入到XML 結構化資料中,進而使資料以極高的效率傳送給最合适的資源。基于XML 建立起來的應用具有良好的語義完整性和擴充性。
  • 分布式的網絡架構。XMPP 協定都是基于Client/Server 架構,但是XMPP協定本身并沒有這樣的限制。網絡的架構和電子郵件十分相似,但沒有結合任何特定的網絡架構,适用範圍非常廣泛。
  • XMPP 具有很好的彈性。XMPP 除了可用在即時通信的應用程式,還能用在網絡管理、内容供稿、協同工具、檔案共享、遊戲、遠端系統監控等。
  • 安全性。XMPP在Client-to-Server通信,和Server-to-Server通信中都使用TLS (Transport Layer Security)協定作為通信通道的加密方法,保證通信的安全。任何XMPP伺服器可以獨立于公衆XMPP網絡(例如在企業内部網絡中),而使用SASL及TLS等技術更加增強了通信的安全性。如下圖所示:
XMPP之Smack 介紹XMPP簡介工作原理代碼抽取

工作原理

XMPP伺服器

XMPP 伺服器遵循兩個主要法則:

(1)監聽用戶端連接配接,并直接與用戶端應用程式通信;

(2)與其他 XMPP 伺服器通信;

XMPP開源伺服器一般被設計成子產品化,由各個不同的代碼包構成,這些代碼包分别處理Session管理、使用者和伺服器之間的通信、伺服器之間的通信、DNS(Domain Name System)轉換、存儲使用者的個人資訊和朋友名單、保留使用者在下線時收到的資訊、使用者注冊、使用者的身份和權限認證、根據使用者的要求過濾資訊和系統記錄等。另外,伺服器可以通過附加服務來進行擴充,如完整的安全政策,允許伺服器元件的連接配接或用戶端選擇,通向其他消息系統的網關。

基本的XMPP 伺服器必須實作以下标準協定:

RFC3920 核心協定Core

RFC3921 即時消息和出席協定Instant Messaging and Presence

XEP-0030 服務發現Service Discovery

XMPP用戶端

XMPP 系統的一個設計标準是必須支援簡單的用戶端。事實上,XMPP 系統架構對用戶端隻有很少的幾個限制。一個XMPP 用戶端必須支援的功能有:

(1)通過 TCP 套接字與XMPP 伺服器進行通信;

(2)解析組織好的 XML 資訊包;

(3)了解消息資料類型。

XMPP 将複雜性從用戶端轉移到伺服器端。這使得用戶端編寫變得非常容易,更新系統功能也同樣變得容易。XMPP 用戶端與服務端通過XML 在TCP 套接字的5222 端口進行通信,而不需要用戶端之間直接進行通信。

基本的XMPP 用戶端必須實作以下标準協定(XEP-0211):

RFC3920 核心協定Core

RFC3921 即時消息和出席協定Instant Messaging and Presence

XEP-0030 服務發現Service Discovery

XEP-0115 實體能力Entity Capabilities

XMPP網關

XMPP 突出的特點是可以和其他即時通信系統交換資訊和使用者線上狀況。由于協定不同,XMPP 和其他系統交換資訊必須通過協定的轉換來實作,目前幾種主流即時通信協定都沒有公開,是以XMPP 伺服器本身并沒有實作和其他協定的轉換,但它的架構允許轉換的實作。實作這個特殊功能的服務端在XMPP 架構裡叫做網關(gateway)。目前,XMPP 實作了和AIM、ICQ、IRC、MSN Massager、RSS0.9 和Yahoo Massager 的協定轉換。由于網關的存在,XMPP 架構事實上相容所有其他即時通信網絡,這無疑大大提高了XMPP 的靈活性和可擴充性。

XMPP位址格式

一個實體在XMPP網絡結構中被稱為一個接點,它有唯一的标示符jabber identifier(JID),即實體位址,用來表示一個Jabber使用者,但是也可以表示其他内容,例如一個聊天室.一個有效的JID包括一系列元素:(1)域名(domain identifier);(2)節點(node identifier);(3)源(resource identifier).它的格式是[email protected]/resource,[email protected],類似電子郵件的位址格式.domain用來表示接點不同的裝置或位置,這個是可選的,例如a在Server1上注冊了一個使用者,使用者名為doom,那麼a的JID就是[email protected],在發送消息時,指明[email protected]就可以了,resource可以不用指定,但a在登入到這個Server時,fl的JID可能是[email protected]、exodus(如果a用Exodus軟體登入),也可能是[email protected]/psi(如果a用psi軟體登入).資源隻用來識别屬于使用者的位置或裝置等,一個使用者可以同時以多種資源與同一個XMPP伺服器連接配接。

XML流

MPP本質上是一種XML流技術。用戶端開始和XMPP伺服器會話,會打開一個長時間的TCP連接配接,然後和伺服器協商一個流。一旦你和你的伺服器建立了一個XML流,你和你的伺服器可以通過流交換三個特殊的XML片段:<message/>,<presence/>,<iq/>.這些片段稱為XML節。是XML中最有意義的基本單元,而且一旦你已建立一個XML流,你可以通過流發送無數個節。

XMPP消息格式

XMPP中定義了3個頂層XML元素: Message、Presence、IQ,下面針對這三種元素進行介紹。

  • <Message>

用于在兩個jabber使用者之間發送資訊。Jsm(jabber會話管理器)負責滿足所有的消息,不管目标使用者的狀态如何。如果使用者線上jsm立即送出;否則jsm就存儲。

To :辨別消息的接收方。

from : 指發送方的名字或标示(id)o

Text: 此元素包含了要送出給目标使用者的資訊。

結構如下所示:

<message to= ‘[email protected]/contact’ type =’chat’>
        <body> 你好,在忙嗎 </body>
        </message> 
           
  • <Presence>

    用來表明使用者的狀态,如:online、away、dnd(請勿打擾)等。當使用者離線或改變自己的狀态時,就會在stream的上下文中插入一個Presence元素,來表明自身的狀态.結構如下所示:

<presence>
        From =‘lily @ jabber.com/contact’
        To = ‘yaoman @ jabber.com/contact'
    <status>Online</status>
    </presence>
           

presence元素可以取下面幾種值:

Probe :用于向接受消息方法發送特殊的請求

subscribe:當接受方狀态改變時,自動向發送方發送presence資訊。

  • <IQ>

    一種請求/響應機制,從一個實體從發送請求,另外一個實體接受請求,并進行響應.例如,client在stream的上下文中插入一個元素,向Server請求得到自己的好友清單,Server傳回一個,裡面是請求的結果.

    其主要屬性是type,包括:

    主要的屬性是type。包括:

    Get :擷取目前域值。

    Set :設定或替換get查詢的值。

    Result :說明成功的響應了先前的查詢。

    Error: 查詢和響應中出現的錯誤。

    結構如下所示:

<iq from =‘lily @ jabber.com/contact’id=’1364564666’ Type=’result’>
           

綁定到TCP

用戶端與伺服器通信的過程中,伺服器必須允許用戶端共享一個TCP連接配接來傳輸XML節,包括從用戶端傳到伺服器和從伺服器傳到用戶端。

伺服器到伺服器的通信過程中,伺服器必須用一個TCP連接配接向對方發送XML節,另一個TCP連接配接(由對方初始化)接受對方的XML節,一共兩個TCP連接配接。

代碼抽取

建立連結

XMPPTCPConnectionConfiguration connectionConfiguration = XMPPTCPConnectionConfiguration.builder()
                .setHost(AppContancts.host)//主機名
                .setPort(AppContancts.port)//端口
                .setServiceName(AppContancts.host)
                .setSendPresence(true)// support presence
                .setConnectTimeout(1000 * 10)
                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//越過證書
                .build();

mConnection = new XMPPTCPConnection(connectionConfiguration);
mConnection.connect();      
           
mConnection.connect(); 需要手動調用此方法

登入

登入有兩種方式:

  • 在建構連結的時候去做登入
XMPPTCPConnectionConfiguration connectionConfiguration = XMPPTCPConnectionConfiguration.builder()
                .setHost(AppContancts.host)
                .setPort(AppContancts.port)
                .setServiceName(AppContancts.host)
                .setSendPresence(true)// support presence
                .setConnectTimeout(1000 * 10)
                .setUsernameAndPassword("Leo","123")
                .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//越過證書
                .build();

        mConnection = new XMPPTCPConnection(connectionConfiguration);
           
setUsernameAndPassword("Leo","123") 調用此方法就可以在連結的時候完成登入。
  • 完成建構後手動調用login()方法
if (mConnection.isConnected()){
                mConnection.login("Leo","123");
            }
           

注冊

注冊時,需要先連接配接成功才能完成注冊
// 注冊關鍵代碼
        AccountManager accountManager = AccountManager.getInstance(mConnection);
        try {
            accountManager.createAccount("lexiaowen", "123");
        } catch (XMPPException e) {
            e.printStackTrace();
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        } catch (SmackException.NoResponseException e) {
            e.printStackTrace();
        }
           

好友管理

  • 首先好友管理由 org.jivesoftware.smack.roster.Roster類進行子產品管理,本質上是用單例模式進行執行個體化,我們可以通過以下代碼進行執行個體化:
mRoster = Roster.getInstanceFor(mConnection);
        try {
            //設定對方添加自己好友,需要詢問
            mRoster.setSubscriptionMode(Roster.SubscriptionMode.manual);
            mRoster.reloadAndWait();
        } catch (SmackException.NotLoggedInException e) {
            e.printStackTrace();
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 //監聽好友狀态(是否線上)、好友添加、查詢好友結果等作用        
        mRoster.getEntriesAndAddListener(this, this);
           
  • RosterListener 顧名思義,這個監聽主要針對好友狀态監聽,例如以下分析:
/**
     * 添加好友的時候,狀态變化回回調,傳回添加好友的XMPP位址集合
     */
    public void entriesAdded(Collection<String> addresses);

    /**
     * 添加好友的時候,狀态變化回回調,傳回添加好友的XMPP位址集合
     */
    public void entriesUpdated(Collection<String> addresses);

    /**
     * 好友資訊更新的時候回調,傳回添加好友的XMPP位址集合。一般可以利用更新本地資料庫
     */
    public void entriesDeleted(Collection<String> addresses);

    /**
     * 删除好友的時候,狀态變化回調,傳回添加好友的XMPP位址集合
     */
    public void presenceChanged(Presence presence);
           
  • 添加好友
private void addRosyer(String user, String name, String[] groupName) {
        if (mConnection.isAuthenticated()) {
            try {
                mRoster.createEntry(user, name, groupName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
           
  • 删除好友
//删除好友
    public void deleteRoster(String user) {
        RosterEntry entry = mRoster.getEntry(user);
        if (entry != null) {
            try {
                mRoster.removeEntry(entry);
            } catch (SmackException.NotLoggedInException |
                    SmackException.NoResponseException |
                    XMPPException.XMPPErrorException |
                    SmackException.NotConnectedException e) {
                e.printStackTrace();
            }
        }
    }
           
  • 添加分組
//添加分組
    public void addGroup(String groupName) {
        mRoster.createGroup(groupName);
    }
           

消息管理

Smack中的基本消息由org.jivesoftware.smack.packet.Message組成,消息内 容存儲在body标簽裡面。

線上消息

  • 擷取聊天管理器
ChatManager mChatManager = ChatManager.getInstanceFor(mConnection);
           
  • 發送消息
Message message = new Message();
                        message.setBody("hello f123");
                        sendMessage(createChat("[email protected]"), message);
           
public Chat createChat(String userJid) {
        ChatManager mChatManager = ChatManager.getInstanceFor(mConnection);
        Chat curChat = null;
        curChat = mChatManager.createChat(userJid);
        curChat.addMessageListener(this);
        return curChat;
    }
           
  • 消息接收
需要實作兩個回調監聽:ChatManagerListener和ChatMessageListener

ChatManagerListener:

void chatCreated(Chat chat, boolean createdLocally); // 聊天會話被建立回調
           

ChatMessageListener:

void processMessage(Chat chat, Message message); // 消息接收回調 
           

繼續閱讀