XMPP詳解
XMPP(eXtensible Messaging and Presence Protocol,可擴充消息處理和現場協定)是一種在兩個地點間傳遞小型結構化資料的協定。在此基礎上,XMPP協定已經被用來建構大規模即時通信系統、遊戲平台、協作空間及語音和視訊會議系統。
XMPP由幾個小的構造塊組成,并在此基礎上擴充出了更多的構造塊。XMPP中有衆多系統:釋出-訂閱服務、多人聊天、表單檢索與處理、服務發現、實時資料傳輸、隐私處理及遠端過程調用等。
大多數社交媒體(Facebook及Twitter)也采用了XMPP協定。
什麼是XMPP
與其他協定一樣,XMPP定義了在兩個或者更多通信實體間傳遞資料所采用的格式。對于XMPP,實體通常是指用戶端伺服器,但是其也允許用戶端與用戶端或伺服器端與伺服器端的通信。
在XMPP上交換的是XML資料,采用這種格式,使XMPP協定獲得了極大的可擴充性,因為使用XML可以友善的新增功能并保證前後向相容。使用XML較二進制協定占用更大的帶寬,但獲得的優勢是具有了幾乎無限的可擴充性。
使用者可以向XMPP Standards Foundation注冊協定擴充。
在XMPP中,XML資料被組織為了一對流,每個流分别對應通信的一個方向。每個XML流均由一個開始元素、後跟XMPP節和其他頂級元素,然後是一個結束元素組成。每個XMPP節(可帶有子元素及屬性)均是該流的一級子元素。在XMPP連接配接末尾,這兩個流形成了一對有效的XMPP文檔。
XMPP節構成了該協定的核心部分,而XMPP應用程式則關注如何發送和響應各種類型的節。節可能包含網絡上其他實體的資訊、類似于電子郵件的個人消息或為計算機處理而設計的結構化資料。
<message to=’[email protected]’ from=’[email protected]/dance’ type=’chat’>
<body>what think you of books</body>
</message>
在一個典型的XMPP會話中,一個上述的節将會從darcy的XMPP用戶端發送到她的XMPP伺服器,她的伺服器将會注意到該節的目的地是某個遠端伺服器上的一個實體,是以與該遠端伺服器先建立XMPP連接配接,并将消息轉發該處。
這種通信網絡與電子郵件類似,但與電子郵件伺服器不同的是,XMPP間的伺服器可以直接通信,而不需要借助中間伺服器。
這種直接通信避免了垃圾資訊的幹擾,且還支援通過TLS(Transport Layer Security,傳輸層安全)來加密通信并通過SASL(Simple Authentication and Security Layers,簡單身份驗證與安全層)實作身份驗證機制。
XMPP使用傳遞短小資訊來設計的,而非針對大型資料塊,但XMPP能夠用來協商并建立可在端點間傳遞大型資料塊的帶内或者帶外傳輸。
XMPP曆史
最初各大公司釋出的ICQ均基于其公司營運的專有協定和網絡(ICQ,Yahoo Pager)。
開發人員企圖互通這些IM的努力由于協定的閉源特性失敗了。
1991年1月Jeremie Miller釋出了Jabber項目,Jabber是基于XML的去中心化的即時通信協定,同時也是一個叫jabberd的伺服器實作。
2000年5月,Jabber的核心協定穩定,jabberd也正式釋出了。
2001年,JSF(Jabber Software Foundation,Jabber軟體基金會)成立,并開始圍繞jabber協定做規範性工作。
2002年12月,JSF向IETF送出了核心規範,并成立了一個IETF小組。
2004年10月,這個标準化程序産生了Jabber協定的改進版,改名為XMPP,其文檔成為RFC标準,編号分别為3920、3921、3922和3923。
最初開發人員向JSF送出的擴充稱為JEP(Jabber Extension Proposal,Jabber擴充提議)。最終随着Jabber過度到XMPP,JSF更名為XSF(XMPP Standard Foundation,XMPP标準基金會)和XEP(XMPP Extension Proposal,XMPP擴充提議)。
2005年XMPP技術開始大規模部署,例如Google Talk。
先進有大約300個XEP,并有數十種用戶端和服務端實作,開源及商用均有,實際上,任何程式設計語言都可以找到這樣一個庫來加速XMPP開發程序。
XMPP網絡
任何XMPP網絡都是由若幹角色組成,可以分為伺服器端、用戶端、元件和伺服器插件。
XMPP網絡與WWW網絡及EMAIL網絡不同,XMPP伺服器之間尋址隻會跳一次,而EMAIL協定則會有多個中轉伺服器,XMPP儲存完整的清單。

伺服器
XMPP伺服器是任何XMPP網絡的通信系統,伺服器的任務就是為XMPP節提供路由。無論這些節是從内部的一個使用者發往另外一個使用者還是本地使用者發送給伺服器。
一組能夠互相通信的XMPP伺服器構成了XMPP網絡。
XMPP伺服器總是允許使用者連接配接到自己,但是也可以編寫直接使用伺服器-伺服器協定的應用和程式,來減輕路由消耗。
Ejabberd、Openfire和Tigase是三種能夠運作在Windows,Mac OS X和Linux的開源伺服器。
M-Link和Jabber XCP是商用産品。
用戶端
大多數XMPP實體均是用戶端,通過用戶端-伺服器協定連接配接到XMPP伺服器。
用戶端必須向某個地方的XMPP伺服器進行身份驗證。伺服器會将該用戶端發送的所有節路由到合适的目的地。
伺服器還負責管理用戶端會話的其他幾個方面,包括花名冊及裸位址。
元件
不僅僅是用戶端能夠連接配接到XMPP伺服器,大多數伺服器還支援外部伺服器元件。這些元件通過添加某種新服務來增強伺服器的行為。這些元件在伺服器内有各自的身份和位址,但運作在外部并通過元件協定通信。
元件協定(XEP-0114)可以讓開發人員以一種伺服器不可知的方式建立伺服器擴充,例如多人聊天服務。
元件也需要向XMPP伺服器進行身份驗證,但要較用戶端的完全SASL驗證簡單,例如密碼。
每個元件程式設計伺服器内部一個可單獨尋址的實體,在外界看類似于一個子伺服器。除了基本節之外,XMPP伺服器不會代替已連接配接元件來管理其他節的路由。
伺服器還允許元件在内部自行路由或管理節,因而更為靈活。
插件
許多XMPP伺服器還支援使用插件進行擴充,但插件深入到伺服器内部,有較高的效率以及最低的通用性。
插件一般是綁定特定類型的伺服器的。
XMPP尋址
XMPP網絡上的每個實體都有一個或多個位址(稱為JID,jabber identifier)。通常類似于:
[email protected]和[email protected]就是兩個JID。
JID由三個部分組成,節點、域和資源,域是必須的,其他兩個部分是可選的。
域是實體(伺服器、元件或插件)可解析的DNS名稱。僅由域組成的JID是有效位址,表示伺服器位址。指向域的節将由伺服器自身處理,并可能被路由到某個元件或插件。
本地部分通常用來識别域中的一個特定使用者,位于@前。本地部分也可以用來識别其他對象,如某個聊天室。
JID的資源部分通常會辨別一個特定用戶端的XMPP連接配接。對于XMPP用戶端而言,每個連接配接均被指派一個資源。如[email protected]想要連接配接他的書法和圖書館則可以通過
[email protected]/study和[email protected]/library來尋址,這樣避免了使用者在打開多個連結時消息無法找到正确的處理器。主要注意的是,資源部分是區分大小寫的。
JID劃分為兩種類型:
- 裸JID
完整JID去除資源部分的位址,用戶端的裸JID有些特殊,這是因為伺服器自己将處理發往用戶端的裸JID節。裸JID可以視為尋址使用者的賬戶,而不是用戶端。
- 完整JID
最為具體的位址
XMPP節
核心XMPP工具集由三個基本節組成,分别為<presence>、<message>和<iq>
XMPP流由兩份XML文檔組成,通信的每個方向均有一個文檔,這些文檔有一個根元素<stream:stream>,<stream:stream>的子元素由可路由的節以及與流相關的頂級子元素構成。
<stream:stream>
<iq type=’get’>
<query xmlns=’jabber:iq:roster’ /> //請求自己的花名冊
</iq>
<presence/> //通知伺服器她已線上并可以通路
<message to=’[email protected]’ from=’[email protected]/ballroom’ type=’chat’>
<body>
I cannot talk of books in a ball-room; my head is always full of something else.
</body> //發送消息
</message>
<presence type=’unavailable’> // 聲明自己不可通路并關閉
</stream:stream>
通用屬性
from/to/type/id
from的屬性并非由用戶端提供,而是服務端進行的标記。
presence節
presence提供網絡實體的可通路性。使用者發出presence節,表明自己上線,這樣可以會有更大的機率與别人通信(人們更願意與線上的人交流),但是我們也不用擔心任何人都可以看到自己的線上狀态,除非我們訂閱了該使用者的狀态,訂閱之後,使用者的狀态資訊會自動發送到訂閱者處。
實際上,XMPP的presence節是一個簡單的專用的釋出-訂閱方法。
在IM中,presence展現在花名冊(roster)中,花名冊儲存有JID清單以及使用者與這些JID的訂閱關系,一旦上線,使用者發送presence節,剩下的就由伺服器處理了(通知自己線上,以及擷取聯系人的狀态資訊)
message節
用于從一個實體向另外一個實體發送消息,并可以傳輸任何類型的結構化資訊,不保證傳輸可靠性
message是一個非常基礎的推模型,message通常用于IM,groupchat,警告和通知等。
message的type有如下幾種:
- normal
類似于email,發出後不等待回應
- chat
用于兩個實體間的實時通信
- groupchat
多使用者聊天室中使用
- headline
用于發送警告或通知
- error
發送錯誤資訊
<message [email protected]/foo [email protected] type="chat">
<body>Who are you?</body>
<subject>Query</subject>
</message>
除了type之外,典型的message節中還包含from、to或者id屬性(用于目的追蹤)。
to中的JID為消息的接受者,from是發送者的JID,但是from屬性并非由用戶端提供,而是發送者的服務端提供的,以避免位址模仿。
message節中也可以包含未在XMPP協定中定義的負載,可以用于擴充。
IQ節
表示Info/Query,為XMPP通信提供請求及響應機制,類似于GET/POST/PUT方法。
IQ隻能包含一個payload,并且定義了需要由伺服器處理的請求或者動作。相對于message來說,IQ具有更好的可靠性,因其要求收到回應。
IQ中包含有id屬性,用于識别伺服器發回的響應。
- get
用于請求資訊,類似于HTTP Get
- set
提供資訊或請求,類似于HTTP POST/PUT
- result
響應請求,類似于HTTP 200
錯誤資訊
例子
- 發送擷取花名冊請求
<iq [email protected]/pda id="rr82a1z7" [email protected] type="get">
<query xmlns="jabber:iq:roster"/>
</iq>
- 伺服器傳回花名冊
<iq [email protected] id="rr82a1z7" [email protected]/pda type="result">
<query xmlns="jabber:iq:roster">
<item jid="[email protected]"/>
<item jid="[email protected]"/>
<item jid="[email protected]"/>
<item jid="[email protected]"/>
</query>
</iq>
- 使用者新增一個聯系人
<iq [email protected]/pda id="ru761vd7" [email protected] type="set">
<query xmlns="jabber:iq:roster">
<item jid="[email protected]"/>
</query>
</iq>
- 伺服器響應
<iq [email protected] id="ru761vd7" [email protected]/pda type="result"/>
error節
具有明确的結構,通常包含原節内容,通用錯誤資訊以及應用程式特有的錯誤條件和資訊(可選)
可擴充
XMPP協定是基于XML的協定,是以其天生提供了很好的可擴充性。我們可以用XMPP傳遞各種資訊,包括連結、位置資訊,Web Service等。
連接配接生命周期
發送XMPP節通常需要建立一個經過身份驗證的XMPP會話,包括連接配接、流的建立、身份驗證以及斷開連接配接。
連接配接
在發送任何節之前,需要建立XMPP流,在XMPP流存在之前,必須建立通往XMPP伺服器的連接配接。
當XMPP用戶端或者伺服器連接配接到另外一個XMPP伺服器時,首先要查詢SRV記錄,該記錄儲存有特定域的伺服器清單。查詢應答中可以包含多條SRV記錄,這樣就可以在多個伺服器中建立負載均衡連接配接。
如果沒有找到合适的SRV記錄,那麼程式将試圖直接連接配接到指定域。
流的建立
一旦建立通過給定XMPP伺服器的連接配接,XMPP流就啟動了
向伺服器發送<stream:stream>,就可以打開XMPP流,伺服器發送響應流的起始标記<stream:stream>進行響應
建立XMPP流之後就可以來回發送各種元素
伺服器發送<stream:feature>元素,列舉XMPP流中支援所有功能,大多數與可用的加密和身份驗證選型有關
身份驗證
XMPP允許進行TLS(Transport Layer Security,傳輸層安全)加密,而且大多數用戶端預設使用該功能。
一旦伺服器通告TLS支援後,用戶端就會啟動TLS連接配接并将目前套接字更新為加密套接字而不斷開連接配接。一旦TLS加密确立,就會建立一對新的XMPP流。
XMPP中的身份驗證使用SASL(Simple Authentication and Security Layers,簡單身份驗證與安全層)協定并支援多種身份驗證機制(取決于伺服器)。
一旦完成身份驗證,用戶端必須為連接配接綁定一個資源并啟動一個會話,通過<bind>和<session>元素發送。
當兩台伺服器互相連接配接時,身份驗證步驟稍稍不同。
連接配接斷開
當使用者結束XMPP會話後,他們終止會話并斷開連接配接,最優雅的方式是首先發送無效出席資訊,然後關閉<stream:stream>元素。
<presence type=’unavailable’>
</steam:stream>