XMPP簡介
原理
-
概述
XMPP(可擴(kuò)展消息處理現(xiàn)場協(xié)議)是基于可擴(kuò)展標(biāo)記語言(XML)的協(xié)議,它用于即時消息(IM)以及在線現(xiàn)場探測。它在促進(jìn)服務(wù)器之間的準(zhǔn)即時操作。這個協(xié)議可能最終允許因特網(wǎng)用戶向因特網(wǎng)上的其他任何人發(fā)送即時消息,即使其操作系統(tǒng)和瀏覽器不同。
XMPP協(xié)議網(wǎng)絡(luò)架構(gòu)
XMPP是一個典型的C/S架構(gòu),而不是像大多數(shù)即時通訊軟件一樣,使用P2P客戶端到客戶端的架構(gòu),也就是說在大多數(shù)情況下,當(dāng)兩個客戶端進(jìn)行通訊時,他們的消息都是通過服務(wù)器傳遞的(也有例外,例如在兩個客戶端傳輸文件時).采用這種架構(gòu),主要是為了簡化客戶端,將大多數(shù)工作放在服務(wù)器端進(jìn)行,這樣,客戶端的工作就比較簡單,而且,當(dāng)增加功能時,多數(shù)是在服務(wù)器端進(jìn)行.XMPP服務(wù)的框架結(jié)構(gòu)如下圖所示.XMPP中定義了三個角色,XMPP客戶端,XMPP服務(wù)器、網(wǎng)關(guān).通信能夠在這三者的任意兩個之間雙向發(fā)生.服務(wù)器同時承擔(dān)了客戶端信息記錄、連接管理和信息的路由功能.網(wǎng)關(guān)承擔(dān)著與異構(gòu)即時通信系統(tǒng)的互聯(lián)互通,異構(gòu)系統(tǒng)可以包括SMS(短信)、MSN、ICQ等.基本的網(wǎng)絡(luò)形式是單客戶端通過TCP/IP連接到單服務(wù)器,然后在之上傳輸XML
主要特點
- XMPP 協(xié)議是公開的,由JSF開源社區(qū)組織開發(fā)的。XMPP 協(xié)議并不屬于任何的機(jī)構(gòu)和個人,而是屬于整個社區(qū),這一點從根本上保證了其開放性。
- XMPP 協(xié)議具有良好的擴(kuò)展性。在XMPP 中,即時消息和到場信息都是基于XML 的結(jié)構(gòu)化信息,這些信息以XML 節(jié)(XML Stanza)的形式在通信實體間交換。XMPP 發(fā)揮了XML 結(jié)構(gòu)化數(shù)據(jù)的通用傳輸層的作用,它將出席和上下文敏感信息嵌入到XML 結(jié)構(gòu)化數(shù)據(jù)中,從而使數(shù)據(jù)以極高的效率傳送給最合適的資源。基于XML 建立起來的應(yīng)用具有良好的語義完整性和擴(kuò)展性。
- 分布式的網(wǎng)絡(luò)架構(gòu)。XMPP 協(xié)議都是基于Client/Server 架構(gòu),但是XMPP協(xié)議本身并沒有這樣的限制。網(wǎng)絡(luò)的架構(gòu)和電子郵件十分相似,但沒有結(jié)合任何特定的網(wǎng)絡(luò)架構(gòu),適用范圍非常廣泛。
- XMPP 具有很好的彈性。XMPP 除了可用在即時通信的應(yīng)用程序,還能用在網(wǎng)絡(luò)管理、內(nèi)容供稿、協(xié)同工具、檔案共享、游戲、遠(yuǎn)端系統(tǒng)監(jiān)控等。
- 安全性。XMPP在Client-to-Server通信,和Server-to-Server通信中都使用TLS (Transport Layer Security)協(xié)議作為通信通道的加密方法,保證通信的安全。任何XMPP服務(wù)器可以獨立于公眾XMPP網(wǎng)絡(luò)(例如在企業(yè)內(nèi)部網(wǎng)絡(luò)中),而使用SASL及TLS等技術(shù)更加增強(qiáng)了通信的安全性。如下圖所示:
工作原理
XMPP服務(wù)器
XMPP 服務(wù)器遵循兩個主要法則:
(1)監(jiān)聽客戶端連接,并直接與客戶端應(yīng)用程序通信;
(2)與其他 XMPP 服務(wù)器通信;
XMPP開源服務(wù)器一般被設(shè)計成模塊化,由各個不同的代碼包構(gòu)成,這些代碼包分別處理Session管理、用戶和服務(wù)器之間的通信、服務(wù)器之間的通信、DNS(Domain Name System)轉(zhuǎn)換、存儲用戶的個人信息和朋友名單、保留用戶在下線時收到的信息、用戶注冊、用戶的身份和權(quán)限認(rèn)證、根據(jù)用戶的要求過濾信息和系統(tǒng)記錄等。另外,服務(wù)器可以通過附加服務(wù)來進(jìn)行擴(kuò)展,如完整的安全策略,允許服務(wù)器組件的連接或客戶端選擇,通向其他消息系統(tǒng)的網(wǎng)關(guān)。
基本的XMPP 服務(wù)器必須實現(xiàn)以下標(biāo)準(zhǔn)協(xié)議:
RFC3920 核心協(xié)議Core
RFC3921 即時消息和出席協(xié)議Instant Messaging and Presence
XEP-0030 服務(wù)發(fā)現(xiàn)Service Discovery
XMPP客戶端
XMPP 系統(tǒng)的一個設(shè)計標(biāo)準(zhǔn)是必須支持簡單的客戶端。事實上,XMPP 系統(tǒng)架構(gòu)對客戶端只有很少的幾個限制。一個XMPP 客戶端必須支持的功能有:
(1)通過 TCP 套接字與XMPP 服務(wù)器進(jìn)行通信;
(2)解析組織好的 XML 信息包;
(3)理解消息數(shù)據(jù)類型。
XMPP 將復(fù)雜性從客戶端轉(zhuǎn)移到服務(wù)器端。這使得客戶端編寫變得非常容易,更新系統(tǒng)功能也同樣變得容易。XMPP 客戶端與服務(wù)端通過XML 在TCP 套接字的5222 端口進(jìn)行通信,而不需要客戶端之間直接進(jìn)行通信。
基本的XMPP 客戶端必須實現(xiàn)以下標(biāo)準(zhǔn)協(xié)議(XEP-0211):
RFC3920 核心協(xié)議Core
RFC3921 即時消息和出席協(xié)議Instant Messaging and Presence
XEP-0030 服務(wù)發(fā)現(xiàn)Service Discovery
XEP-0115 實體能力Entity Capabilities
XMPP網(wǎng)關(guān)
XMPP 突出的特點是可以和其他即時通信系統(tǒng)交換信息和用戶在線狀況。由于協(xié)議不同,XMPP 和其他系統(tǒng)交換信息必須通過協(xié)議的轉(zhuǎn)換來實現(xiàn),目前幾種主流即時通信協(xié)議都沒有公開,所以XMPP 服務(wù)器本身并沒有實現(xiàn)和其他協(xié)議的轉(zhuǎn)換,但它的架構(gòu)允許轉(zhuǎn)換的實現(xiàn)。實現(xiàn)這個特殊功能的服務(wù)端在XMPP 架構(gòu)里叫做網(wǎng)關(guān)(gateway)。目前,XMPP 實現(xiàn)了和AIM、ICQ、IRC、MSN Massager、RSS0.9 和Yahoo Massager 的協(xié)議轉(zhuǎn)換。由于網(wǎng)關(guān)的存在,XMPP 架構(gòu)事實上兼容所有其他即時通信網(wǎng)絡(luò),這無疑大大提高了XMPP 的靈活性和可擴(kuò)展性。
XMPP地址格式
一個實體在XMPP網(wǎng)絡(luò)結(jié)構(gòu)中被稱為一個接點,它有唯一的標(biāo)示符jabber identifier(JID),即實體地址,用來表示一個Jabber用戶,但是也可以表示其他內(nèi)容,例如一個聊天室.一個有效的JID包括一系列元素:(1)域名(domain identifier);(2)節(jié)點(node identifier);(3)源(resource identifier).它的格式是node@domain/resource,node@domain,類似電子郵件的地址格式.domain用來表示接點不同的設(shè)備或位置,這個是可選的,例如a在Server1上注冊了一個用戶,用戶名為doom,那么a的JID就是doom@serverl,在發(fā)送消息時,指明doom@serverl就可以了,resource可以不用指定,但a在登錄到這個Server時,fl的JID可能是doom@serverl、exodus(如果a用Exodus軟件登錄),也可能是doom@serverl/psi(如果a用psi軟件登錄).資源只用來識別屬于用戶的位置或設(shè)備等,一個用戶可以同時以多種資源與同一個XMPP服務(wù)器連接。
XML流
MPP本質(zhì)上是一種XML流技術(shù)。客戶端開始和XMPP服務(wù)器會話,會打開一個長時間的TCP連接,然后和服務(wù)器協(xié)商一個流。一旦你和你的服務(wù)器建立了一個XML流,你和你的服務(wù)器可以通過流交換三個特殊的XML片段:<message/>,<presence/>,<iq/>.這些片段稱為XML節(jié)。是XML中最有意義的基本單元,而且一旦你已建立一個XML流,你可以通過流發(fā)送無數(shù)個節(jié)。
XMPP消息格式
XMPP中定義了3個頂層XML元素: Message、Presence、IQ,下面針對這三種元素進(jìn)行介紹。
- <Message>
用于在兩個jabber用戶之間發(fā)送信息。Jsm(jabber會話管理器)負(fù)責(zé)滿足所有的消息,不管目標(biāo)用戶的狀態(tài)如何。如果用戶在線jsm立即提交;否則jsm就存儲。
To :標(biāo)識消息的接收方。
from : 指發(fā)送方的名字或標(biāo)示(id)o
Text: 此元素包含了要提交給目標(biāo)用戶的信息。
結(jié)構(gòu)如下所示:
<message to= ‘lily@jabber.org/contact’ type =’chat’>
<body> 你好,在忙嗎 </body>
</message>
-
<Presence>
用來表明用戶的狀態(tài),如:online、away、dnd(請勿打擾)等。當(dāng)用戶離線或改變自己的狀態(tài)時,就會在stream的上下文中插入一個Presence元素,來表明自身的狀態(tài).結(jié)構(gòu)如下所示:
<presence>
From =‘lily @ jabber.com/contact’
To = ‘yaoman @ jabber.com/contact'
<status>Online</status>
</presence>
presence元素可以取下面幾種值:
Probe :用于向接受消息方法發(fā)送特殊的請求
subscribe:當(dāng)接受方狀態(tài)改變時,自動向發(fā)送方發(fā)送presence信息。
-
<IQ>
一種請求/響應(yīng)機(jī)制,從一個實體從發(fā)送請求,另外一個實體接受請求,并進(jìn)行響應(yīng).例如,client在stream的上下文中插入一個元素,向Server請求得到自己的好友列表,Server返回一個,里面是請求的結(jié)果.
其主要屬性是type,包括:
主要的屬性是type。包括:
Get :獲取當(dāng)前域值。
Set :設(shè)置或替換get查詢的值。
Result :說明成功的響應(yīng)了先前的查詢。
Error: 查詢和響應(yīng)中出現(xiàn)的錯誤。
結(jié)構(gòu)如下所示:
<iq from =‘lily @ jabber.com/contact’id=’1364564666’ Type=’result’>
綁定到TCP
客戶端與服務(wù)器通信的過程中,服務(wù)器必須允許客戶端共享一個TCP連接來傳輸XML節(jié),包括從客戶端傳到服務(wù)器和從服務(wù)器傳到客戶端。
服務(wù)器到服務(wù)器的通信過程中,服務(wù)器必須用一個TCP連接向?qū)Ψ桨l(fā)送XML節(jié),另一個TCP連接(由對方初始化)接受對方的XML節(jié),一共兩個TCP連接。
代碼抽取
建立鏈接
XMPPTCPConnectionConfiguration connectionConfiguration = XMPPTCPConnectionConfiguration.builder()
.setHost(AppContancts.host)//主機(jī)名
.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(); 需要手動調(diào)用此方法
登錄
登錄有兩種方式:
- 在構(gòu)建鏈接的時候去做登錄
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") 調(diào)用此方法就可以在鏈接的時候完成登錄。
- 完成構(gòu)建后手動調(diào)用login()方法
if (mConnection.isConnected()){
mConnection.login("Leo","123");
}
注冊
注冊時,需要先連接成功才能完成注冊
// 注冊關(guān)鍵代碼
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類進(jìn)行模塊管理,本質(zhì)上是用單例模式進(jìn)行實例化,我們可以通過以下代碼進(jìn)行實例化:
mRoster = Roster.getInstanceFor(mConnection);
try {
//設(shè)置對方添加自己好友,需要詢問
mRoster.setSubscriptionMode(Roster.SubscriptionMode.manual);
mRoster.reloadAndWait();
} catch (SmackException.NotLoggedInException e) {
e.printStackTrace();
} catch (SmackException.NotConnectedException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//監(jiān)聽好友狀態(tài)(是否在線)、好友添加、查詢好友結(jié)果等作用
mRoster.getEntriesAndAddListener(this, this);
- RosterListener 顧名思義,這個監(jiān)聽主要針對好友狀態(tài)監(jiān)聽,例如以下分析:
/**
* 添加好友的時候,狀態(tài)變化回回調(diào),返回添加好友的XMPP地址集合
*/
public void entriesAdded(Collection<String> addresses);
/**
* 添加好友的時候,狀態(tài)變化回回調(diào),返回添加好友的XMPP地址集合
*/
public void entriesUpdated(Collection<String> addresses);
/**
* 好友信息更新的時候回調(diào),返回添加好友的XMPP地址集合。一般可以利用更新本地數(shù)據(jù)庫
*/
public void entriesDeleted(Collection<String> addresses);
/**
* 刪除好友的時候,狀態(tài)變化回調(diào),返回添加好友的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組成,消息內(nèi) 容存儲在body標(biāo)簽里面。
在線消息
- 獲取聊天管理器
ChatManager mChatManager = ChatManager.getInstanceFor(mConnection);
- 發(fā)送消息
Message message = new Message();
message.setBody("hello f123");
sendMessage(createChat("f123@192.168.99.212"), message);
public Chat createChat(String userJid) {
ChatManager mChatManager = ChatManager.getInstanceFor(mConnection);
Chat curChat = null;
curChat = mChatManager.createChat(userJid);
curChat.addMessageListener(this);
return curChat;
}
- 消息接收
需要實現(xiàn)兩個回調(diào)監(jiān)聽:ChatManagerListener和ChatMessageListener
ChatManagerListener:
void chatCreated(Chat chat, boolean createdLocally); // 聊天會話被創(chuàng)建回調(diào)
ChatMessageListener:
void processMessage(Chat chat, Message message); // 消息接收回調(diào)