XMPP簡介
原理
-
概述
XMPP(可擴展消息處理現場協議)是基于可擴展標記語言(XML)的協議,它用于即時消息(IM)以及在線現場探測。它在促進服務器之間的準即時操作。這個協議可能最終允許因特網用戶向因特網上的其他任何人發送即時消息,即使其操作系統和瀏覽器不同。
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服務器
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).它的格式是node@domain/resource,node@domain,類似電子郵件的地址格式.domain用來表示接點不同的設備或位置,這個是可選的,例如a在Server1上注冊了一個用戶,用戶名為doom,那么a的JID就是doom@serverl,在發送消息時,指明doom@serverl就可以了,resource可以不用指定,但a在登錄到這個Server時,fl的JID可能是doom@serverl、exodus(如果a用Exodus軟件登錄),也可能是doom@serverl/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= ‘lily@jabber.org/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("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;
}
- 消息接收
需要實現兩個回調監聽:ChatManagerListener和ChatMessageListener
ChatManagerListener:
void chatCreated(Chat chat, boolean createdLocally); // 聊天會話被創建回調
ChatMessageListener:
void processMessage(Chat chat, Message message); // 消息接收回調