本文來轉自:
http://www.ibm.com/developerworks/cn/xml/tutorials/x-realtimeXMPPtut/section3.html
XMPP 簡介
本小節將簡要介紹 XMPP,它的起源,以及為何它是一個適合實時 web 通信的協議。您將檢查 XMPP 通信設置的組件,並查看展示這些組件如何使用的示例。
XMPP 是一組基於 XML 的技術,用於實時應用程序。最初,XMPP 作為一個框架開發,目標是支持企業環境內的即時消息傳遞和聯機狀態應用程序。當時的即時消息傳遞網絡是私有的,非常不適合企業使用。例如,AOL Instant Messenger 不能針對公司內的安全通信進行調整。盡管存在一些商業解決方案,但它們固定的特性集通常不能進行調整,以滿足組織的特殊需求。XMPP,當時名為 Jabber,允許組織構建自己的定制工具來促進實時通信,並允許安裝現成的第三方解決方案。
XMPP 是一個分散型通信網絡,這意味着,只要網絡基礎設施允許,任何 XMPP 用戶都可以向其他任何 XMPP 用戶傳遞消息。多個 XMPP 服務器也可以通過一個專門的 “服務器-服務器” 協議相互通信,提供了創建分散型社交網絡和協作框架的有趣可能性,但這個主題已超出了本教程的討論范圍。
顧名思義,XMPP 可用於滿足廣泛的、對時間敏感的特性要求。實際上,Google Wave,一個大型多用戶協作環境,將 XMPP 作為其聯合協議的基礎。盡管 XMPP 的出現是為了滿足 “個人-個人” 即時消息傳遞的要求,但它完全不必局限於此任務。
要促進消息傳遞,每個 XMPP 客戶端用戶必須擁有一個全局惟一標識符。基於歷史原因,這些標識符稱為 Jabber IDs,或稱為 JIDs。鑒於這個協議的分布式特征,重要的是 JID 應包含聯系用戶所需的所有信息:不存在將用戶鏈接到他們連接到的服務器的中央知識庫。JID 的結構類似於電子郵件地址(但不要求 JID 同時也是有效的電子郵件收件人)。
客戶端和服務器節點,我將它們統稱為 XMPP 實體,都擁有 JIDs。SomeCorp 公司的員工 John Doe 可能擁有 JIDJohn.Doe@somecorp.com
。這里,somecorp.com
是 SomeCorp 公司的 XMPP 服務器的地址,John.Doe
是 John Doe 的用戶名。
JIDs 還擁有連接到它們的資源。這允許在一個 XMPP 實體標識符之外進一步處理細粒度;例如,盡管上面的示例總體上能夠表示 John Doe,但 John.Doe@somecorp.com/Work
可以用於將數據發送到與他的工作相關的工具。
這些資源可以采用任意用戶定義的名稱,一個 XMPP 實體可以擁有任意數量的資源。除了可以是上下文依賴的外,它們還可以綁定到設備、工具或工作站。對於您的 Pingstream 示例,web 站點的每個訪問者都將作為同一個用戶登錄 XMPP 服務器,但他們擁有不同的資源。
使用 XMPP 的實時消息傳遞系統包含三大通信類別:
- 消息傳遞,其中數據在有關各方之間傳輸;
- 聯機狀態,它允許用戶廣播其在線狀態和可用性;
- 信息/查詢請求,它允許 XMPP 實體發起請求並從另一個實體接收響應。
這些類別是互補的。例如,如果用戶或實體離線(盡管在許多用例中,理想的狀態是服務器在用戶返回之前一直持有用戶的消息),則沒有將數據發送給用戶或發起一個實體的信息/查詢請求的點。這些消息中的每一條都將通過一個完整的 XML節 傳遞 — XML 節是以 XML 表達的獨立信息項。
這三種類型的 XMPP 節都擁有以下公共屬性:
from
:源 XMPP 實體的 JID;to
:目標接收者的 JID;id
:這次對話的可選標識符;type
:節的可選子類型;xml:lang
:如果內容是人們可讀的,則為消息語言的描述。
基於 XMPP 的數據傳輸發生在一些 XML 流上,默認在端口 5222 上操作。這些 XML 流實際上是兩個完整的 XML 文檔,每個文檔對應一個通信方向。一旦會話建立,stream
元素將打開。這個元素將封裝整個通信文檔。然后,一些節被注入這個文檔的第二層。最后,一旦通信結束,stream
元素將關閉,形成一個完整的文檔。
例如,清單 1 展示了一個 stream
元素,它建立了從客戶端到服務器的通信。
清單 1. 建立從客戶端到服務器的通信的 stream 標記
<stream:stream from="[server]" id="[unique ID over conversation]" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> |
一旦通信建立,客戶端就能使用 message
元素將消息發送到另一個用戶,message 元素包含以下任意子元素:
subject
:一個可讀的字符串,表示消息主題。body
:一個可讀的字符串,表示消息體。如果每個消息體標記都擁有一個不同的xml:lang
值,那么可以包含多個消息體標記。(xml:lang
是惟一可能的屬性。)thread
:一個惟一標識符,表示一個消息線程。客戶端軟件可以使用這個子元素將相關消息串聯在一起。
但是,消息也可以非常簡單,如 清單 2 所示:
<message from="sendinguser@somedomain" to="recipient@somedomain" xml:lang='en'> <body> Body of message </body> </message> |
對於提供實時 web 界面而言,消息節是最有用的節。“發布-訂閱” 模型 — 在實時 web 應用程序中使用消息來傳輸數據的一種替代方法 — 將稍后介紹。
信息/查詢節擁有廣泛的功能。一個例子就是 “發布-訂閱” 模型,在該模型中,發布者通知服務器某個特定資源進行了更新,服務器則通知已選擇訂閱這些通知並擁有適當授權的所有 XMPP 用戶。
來自發布者的一系列項目被編碼為一些節,格式為基於 XML 的 Atom 發布格式。每個項目都包含在一個 item
元素內,然后合並到一個 pubsub
元素中,最后成為一個信息/查詢節。在 清單 3(選自 XMPP 發布-訂閱規范)中,Shakespeare's Hamlet(JID 為 hamlet@denmark.lit/blogbot
)用他著名的獨白發布一個更新到 pubsub.shakespeare.lit
pubsub 更新節點:
清單 3. 對 pubsub.shakespeare.lit
pubsub 更新節點的更新
<iq type="set" from="hamlet@denmark.lit/blogbot" to="pubsub.shakespeare.lit" id="pub1"> <pubsub xmlns="http://jabber.org/protocol/pubsub"> <publish node="princely_musings"> <item> <entry xmlns="http://www.w3.org/2005/Atom"> <title>Soliloquy</title> <summary> To be, or not to be: that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? </summary> <link rel="alternate" type="text/html" href="http://denmark.lit/2003/12/13/atom03"/> <id>tag:denmark.lit,2003:entry-32397</id> <published>2003-12-13T18:30:02Z</published> <updated>2003-12-13T18:30:02Z</updated> </entry> </item> </publish> </pubsub> </iq> |
信息/查詢節也用於請求一個特定 XMPP 實體的有關信息。例如,在 清單 4 中的節中,boreduser@somewhere
正在查找friendlyuser@somewhereelse
擁有的公共項目。
清單 4. 用戶查找由 friendlyuser@somewhereelse
擁有的公共項目
<iq type="get" from="boreduser@somewhere" to="friendlyuser@somewhereelse" id="publicStuff"> <query xmlns="http://jabber.org/protocol/disco#items"/> </iq> |
反過來,friendlyuser@somewhereelse
使用一列可被訂閱到使用 “發布-訂閱” 的項目進行響應,如 清單 5 所示:
<iq type="result" from="friendlyuser@somewhereelse" to="boreduser@somewhere" id="publicStuff"> <query xmlns="http://jabber.org/protocol/disco#items"> <item jid="stuff.to.do" name="Things to do"/> <item jid="stuff.to.not.do" name="Things to avoid doing"/> </query> </iq> |
在 清單 5 中的信息/查詢節中的每個返回項目都擁有一個可以訂閱到的 JID。信息/查詢還允許超出本教程范圍的廣泛的服務器信息請求。它們中的許多在針對多服務器環境的 web 應用程序上下文中有用,或者作為復雜的分散型協作框架的基礎。
聯機狀態信息包含在一個聯機狀態(presence)節中。如果 type
屬性省略,那么 XMPP 客戶端應用程序假定用戶在線且可用。否則,type
可設置為 unavailable
,或者特定於 pubsub 的值:subscribe
、subscribed
、unsubscribe
和unsubscribed
。它也可以是針對另一個用戶的聯機狀態信息的一個錯誤或探針。
一個聯機狀態節可以包含以下子元素:
show
:一個機器可讀的值,表示要顯示的在線狀態的總體類別。這可以是away
(暫時離開)、chat
(可用且有興趣交流)、dnd
(請勿打擾)、或xa
(長時間離開)。status
:一個可讀的 show 值。該值為用戶可定義的字符串。priority
:一個位於 -128 到 127 之間的值,定義消息路由到用戶的優先順序。如果值為負數,用戶的消息將被扣留。
例如,清單 6 中的 boreduser@somewhere
可以用這個節來表明聊天意願:
<presence xml:lang="en"> <show>chat</show> <status>Bored out of my mind</status> <priority>1</priority> </presence> |
注意 from
屬性此處省略。
另一個用戶 friendlyuser@somewhereelse
可以通過發送 清單 7 中的節來探測 boreduser@somewhere
的狀態:
<presence type="probe" from="friendlyuser@somewhereelse" to="boreduser@somewhere"/> Boreduser@somewhere's server would then respond with a tailored presence response: <presence xml:lang="en" from="boreduser@somewhere" to="friendlyuser@somewhereelse"> <show>chat</show> <status>Bored out of my mind</status> <priority>1</priority> </presence> |
這些聯機狀態值源自 “個人-個人” 消息傳遞軟件。show
元素的值 — 通常用於確定將向其他用戶顯示的狀態圖標 — 在聊天應用程序之外如何使用現在還不清楚。狀態值可能會在微博工具中找到用武之地;例如,Google Talk(一個 XMPP 聊天服務)中的用戶狀態字段的更改可以被導入為 Google Buzz 中的微博條目。
另一種可能性就是將狀態值用作每用戶應用程序狀態數據的攜帶者。盡管此規范將狀態定義為可讀,但沒有什么能夠阻止您在那里存儲任意字符串來滿足您的要求。對於某些應用程序而言,它可以不是可讀的,或者,它可以攜帶微格式形態的數據負載。
您可以為一個 XMPP 實體擁有的每個資源獨立設置聯機狀態信息,以便訪問和接收連接到一個應用程序中的單個用戶的所有工具和上下文的數據只需一個用戶帳戶。每個資源都可以被分配一個獨立的優先級;XMPP 服務器將首先嘗試將消息傳遞給優先級較高的資源。
要通過使用 JavaScript 的 XMPP 進行通信的 web 應用程序必須符合一些特殊要求。出於安全考慮,不允許 JavaScript 從 web 頁面的域與不同域上的多個服務器通信。如果您的 web 應用程序界面被托管在 application.mydomain.com
,所有 XMPP 通信也必須發生在 application.mydomain.com
。
防火牆是另一個問題所在。理想情況下,如果您將 XMPP 用作您的 web 界面的實時元素的基礎,那么您希望它對防火牆后面的用戶有效。但是,公司防火牆通常只對少數幾個協議開放幾個端口,以便允許 web 數據、電子郵件和類似的通信通過。默認情況下,XMPP 使用端口 5222,這很可能是公司防火牆阻止的端口。
假設您知道您的用戶前面的防火牆在端口 80 上允許 HTTP(這是用於訪問 web 的默認協議和端口)。理想情況是您的 XMPP 通信能夠越過該端口上的 HTTP。但是,HTTP 的設計並不針對持續連接。web 的架構不同於實時數據所需的通信架構。
下面我們看看 Bidirectional-streams Over Synchronous HTTP (BOSH) 的標准,該標准為雙向同步數據提供一個模擬層。借助這個標准,可以與一個 XMPP 服務器建立一個較長的 HTTP 連接(時長一分鍾或兩分鍾)。如果新數據在那個期間到達,則 HTTP 請求返回數據並關閉;否則,該請求只是失效。不管是哪種情況,一旦一個請求關閉,另一個請求將重新建立。盡管結果是對一個 web 服務器的一系列重復連接,但它是一個比 Ajax 輪詢更有效的數量級,特別是因為連接到的是一個專業服務器而不是直接連接到 web 應用程序。
BOSH 上的 XMPP 允許 web 應用程序通過一個原生連接持續與 XMPP 服務器通信。客戶端通過端口 80 上的 HTTP 上的一個標准 URL 連接。然后,web 服務器將這個連接代理到由 XMPP 服務器操作的一個不同端口 — 通常是 7070 — 上的 HTTP URL。這樣,無論何時數據被發送到 XMPP 服務器,web 應用程序只需使用一些資源,而 web 客戶端可以使用通常支持的 web 標准從防火牆后操作。維持 BOSH 的較長 HTTP 輪詢的開銷主要由 XMPP 服務器而不是 web 服務器或 web 應用程序承擔。web 服務器和 XMPP 服務器都不會受到與使用 JavaScript 進行通信一樣的域限制,正是因為這一點,消息才能夠被發送到其他 XMPP 服務器和客戶端。
現在,您理解了 XMPP 如何適合實時 web,可以下載並設置它,以便開始創建這個 Pingstream 應用程序。