基於XMPP的即時通信系統的建立
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 [email protected] [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由三個部分組成,節點、域和資源,域是必須的,其他兩個部分是可選的。
域是實體(服務器、組件或插件)可解析的DNS名稱。僅由域組成的JID是有效地址,表示服務器地址。指向域的節將由服務器自身處理,並可能被路由到某個組件或插件。
本地部分通常用來識別域中的一個特定用戶,[email protected],如某個聊天室。
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 [email protected] [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
- error
錯誤信息
例子
- 發送獲取花名冊請求
<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>
基於XMPP的即時通信系統的建立