使用 XMPP 構建一個基于 web 的通知工具 轉
使用 XMPP 構建一個基于 web 的通知工具
使用 XMPP、PHP 和 JavaScript 編寫實時 web 應用程序
Ben Werdmuller , 顧問和作者, Freelance
Ben Werdmuller 是一位 Web 策劃師和開發人員,他專注于開放源碼平臺。他是開源社交網絡框架 Elgg 的共同創始人和技術帶頭人。Ben 的博客? http://benwerd.com/ 。
簡介: ?實時 web 應用程序是聯網的應用程序,帶有基于 web 的用戶界面,能夠及時顯示剛剛發布的 Internet 信息。這樣的應用程序示例包括社會新聞聚合器和監控工具,它們能夠使用來自外部源的數據持續更新。在本教程中,您將創建一個小型通知工具 Pingstream,它使用 PHP 和 JavaScript 通過 Extensible Messaging and Presence Protocol (XMPP) 進行通信,XMPP 是一組設計用于支持聯機狀態和實時通信功能的 XML 技術。
開始之前
本教程將向您介紹實時 web,并詳細介紹之所以要構建實時 web 應用程序的幾個原因。您將學到一些技術,這些技術將幫助您創建響應及時、持續更新的 web 應用程序,這些應用程序既能保護服務器資源,又能提供良好的用戶體驗。
關于本教程
常用縮略詞
DOM: 文檔對象模型
HTML: 超文本標記語言
HTTP: 超文本傳輸協議
REST: 具象狀態傳輸
RSS: 真正簡單聚合
URL: 統一資源定位符
XML: 可擴展標記語言
實時 web 應用程序允許用戶在信息發布時及時接收通知,無需手動檢查原始源獲取更新。通過 Twitter 和 Friendfeed 這樣的社交通知工具,Google Wave 這樣的基于 web 的協作工具,以及 Meebo 這樣的基于 web 的聊天客戶端,實時 web 應用程序逐漸流行起來。
Extensible Messaging and Presence Protocol (XMPP) 是一組基于 XML 的技術,用于實時應用程序,定義為持續更新以響應新數據或更改數據的聯網應用程序。它最初作為一個框架研發,以支持企業環境內的實時消息傳遞和聯機狀態(presence)應用程序。
在本教程中,您將構建一個簡單的工具 Pingstream,它在 RSS 提要更新發布時使用它們持續更新自身(參見? 下載 ?獲取 Pingstream 源代碼)。在此過程中,您將:
了解 XMPP 為何特別適合 web 應用程序;
了解 XMPP 通信的組件;
安裝和配置 Openfire XMPP 服務器;
使用 PHP 和 XMPPHP 庫連接到 XMPP 服務器;
檢查并通過 XMPP 傳輸 RSS 提要中的新項目;
借助 Bidirectional-streams Over Synchronous HTTP (BOSH),使用 Strophe 和 jQuery 通過 HTTP 連接到 XMPP 服務器;
在 web 頁面中顯示 XMPP 通知。
先決條件
本教程假定您比較熟悉使用 PHP 開發 web 應用程序,但也會涉及一些高級編程方法。您還應該擁有一定的 HTML 和 JavaScript 經驗。擁有 jQuery JavaScript 框架經驗可能會有所幫助。但本教程不要求熟悉 XMPP 或類似的技術。
要跟隨本教程操作,必須安裝和運行以下服務器軟件:
PHP 5.2 或更高版本
Apache HTTP Server
MySQL
在本教程中,您還將下載和安裝以下軟件和庫:
Openfire
jQuery
Strophe
XMPPHP
Last RSS
您可能會發現 MySQL 服務器工具 phpMyAdmin 能夠派上用場。如果您使用一臺桌面機器來本地測試您的實時 web 應用程序,您可能會發現 XAMPP 對于管理一個測試 web 服務器基礎設施的安裝和運行很有用。
實時 web 簡介
在本小節中,您將了解什么是實時 web 應用程序,您為何可能需要構建這樣的應用程序,以及它們與典型的現代 web 應用程序模型的區別何在。
接受輸入并提供反饋
實時 web 并不實時
持續更新的 ?web? 應用程序 ?這個詞匯比 實時 ?web? 應用程序 ?更貼切。在計算機科學中, 硬 ?和 軟 ?實時系統都必須滿足操作期限的要求。當任務不能在其分配時間內完成時,硬實時系統將失敗。實時 web 應用程序與軟實時系統更相似,在軟實時系統中,某個功能的延遲并不會導致系統失敗(但可能會降低性能)。但您不應對實時 web 應用程序分配嚴格的任務計劃。目前,web 并不是適合時間關鍵型應用程序的平臺。
應用程序是幫助用戶執行任務的專門軟件,其特征是:從用戶或其他源接收輸入,然后提供可讀的輸出。應用程序也可能動態響應 — 以可視或編程方式 — 自動接收的輸入數據中的變化。例如,當包含特定關鍵字的新聞出現在一個新聞監控應用程序連接到的新聞專線中時,該應用程序可能會通知用戶。
由于 web 的起源是作為一個文檔服務平臺,因此它沒有針對應用程序優化。HTML 非常適合表示和超鏈接文本內容,但不適合創建動態界面。web 應用程序能夠接收和響應用戶輸入,這要歸功于 PHP 這樣的服務器端腳本語言與表單和 JavaScript 這樣的 web 輸入技術的結合。但是,要創建 自動 ?更新的界面,您必須克服一些障礙。這些障礙比較難以克服,因為沒有任何 web 技術在研發時考慮到這個功能。相比之下(以新聞監控為例),桌名軟件無需刷新其界面就能夠將通知發送給用戶;桌面軟件可以持續更新自身。相反,web 則局限于基于頁面的模型。
然而,基于 web 的實時應用程序還是可以實現的,而且它們的好處顯而易見。這樣的應用程序包括企業聊天工具、聯網的實時文檔協作工具、以及搜索界面,它們能夠及時顯示新發布的內容。
面向健壯的應用程序開發的 Web 技術
通常,web 應用程序通過使用 Asynchronous JavaScript and XML (Ajax) 工具來模擬持續更新的界面。在這個模型中,應用程序的 web 頁面中包含 JavaScript,它在后臺反復請求一個服務器回撥。盡管 Ajax 應用程序的響應性在很多情況下都夠用,但這種技術還是有一些缺陷。
Ajax 不能容忍不穩定的 Internet 連接:一次臨時掉線可能會導致整個界面失敗。它在服務器負載方面也效率低下。假設您的后臺 Ajax 輪詢函數每 10 秒檢查一次服務器。每一次都將建立一個新的 HTTP 連接,包括初始化服務請求所需的資源,即使沒有新數據可以向用戶顯示。結果是應用程序不必要地使用過多的處理器時間和帶寬。
基于 XML 的技術向 web 應用程序提供了巨大的優勢。XML 解析器現在是大多數環境的一個標準組成部分;無需其他軟件就能支持以適當的格式讀寫數據。XML 是自我描述型語言;使用它的文檔不需要外部架構。最后,正如 web 是獨立于平臺的一樣,XML 作為一種技術支持在不同平臺間互操作。因此,開發人員可以將精力集中于特定于他們的應用程序的邏輯。
web 基于 HTML、Cascading Style Sheets (CSS) 和 JavaScript 等可互操作的免費開源標準。如果針對 web 上的實時通信的新標準出現,那么該標準也應是免費、開源和可互操作的。基于 XML 的 XMPP 滿足這些標準。在本教程中,您將使用 XMPP 來構建一個客戶端庫,它通過標準方法(比如一個 web hook)來接收輸入,并將適當的數據實時中繼到用戶。
XMPP 簡介
本小節將簡要介紹 XMPP,它的起源,以及為何它是一個適合實時 web 通信的協議。您將檢查 XMPP 通信設置的組件,并查看展示這些組件如何使用的示例。
Web 標準和 XMPP
XMPP 是一組基于 XML 的技術,用于實時應用程序。最初,XMPP 作為一個框架開發,目標是支持企業環境內的即時消息傳遞和聯機狀態應用程序。當時的即時消息傳遞網絡是私有的,非常不適合企業使用。例如,AOL Instant Messenger 不能針對公司內的安全通信進行調整。盡管存在一些商業解決方案,但它們固定的特性集通常不能進行調整,以滿足組織的特殊需求。XMPP,當時名為 Jabber,允許組織構建自己的定制工具來促進實時通信,并允許安裝現成的第三方解決方案。
XMPP 是一個分散型通信網絡,這意味著,只要網絡基礎設施允許,任何 XMPP 用戶都可以向其他任何 XMPP 用戶傳遞消息。多個 XMPP 服務器也可以通過一個專門的 “服務器-服務器” 協議相互通信,提供了創建分散型社交網絡和協作框架的有趣可能性,但這個主題已超出了本教程的討論范圍。
顧名思義,XMPP 可用于滿足廣泛的、對時間敏感的特性要求。實際上,Google Wave,一個大型多用戶協作環境,將 XMPP 作為其聯合協議的基礎。盡管 XMPP 的出現是為了滿足 “個人-個人” 即時消息傳遞的要求,但它完全不必局限于此任務。
XMPP 通信的結構
要促進消息傳遞,每個 XMPP 客戶端用戶必須擁有一個全局惟一標識符。基于歷史原因,這些標識符稱為 Jabber IDs,或稱為 JIDs。鑒于這個協議的分布式特征,重要的是 JID 應包含聯系用戶所需的所有信息:不存在將用戶鏈接到他們連接到的服務器的中央知識庫。JID 的結構類似于電子郵件地址(但不要求 JID 同時也是有效的電子郵件收件人)。
客戶端和服務器節點,我將它們統稱為? XMPP? 實體 ,都擁有 JIDs。SomeCorp 公司的員工 John Doe 可能擁有 JID John.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" /> <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 服務器將首先嘗試將消息傳遞給優先級較高的資源。
XMPP 使用 BOSH 越過 HTTP
要通過使用 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 應用程序。
獲取和安裝一個 XMPP 服務器
在本小節中,您將安裝 Openfire XMPP 服務器并配置它來支持您的實時 web 應用程序。
選擇一個 XMPP 服務器
有兩個領先的開源 XMPP 服務器可以免費下載。它們都應用廣泛并通過 GNU Public License version 2 許可,每個服務器都有自己的優勢和缺點:
l ejabberd:ejabberd 中的? e ?指的是 Erlang,一種軟實時編程語言。這一技術基石使 ejabberd 非常快。它還與 XMPP 核心和相關標準高度兼容。ejabberd 可以安裝在大多數環境中。
l Openfire:Openfire 用 Java? 語言編寫,用戶友好,安裝方便。
本教程使用 Openfire。
創建 Openfire 數據庫
為您的 Openfire 用戶和配置創建一個新的 MySQL 數據庫。通過使用 MySQL,您可以以編程方式從您的 PHP web 應用程序添加、編輯、刪除和查詢您的 XMPP 服務器用戶,以及調節您的 XMPP 基礎設施以匹配您的 web 基礎設施。
如果您安裝了 phpMyAdmin,比如作為您的 XAMPP 安裝的一部分,那么您可以按照以下步驟創建數據庫:
1. 從主界面選擇? Privileges 。
2. 選擇? Add a new user 。
3. 添加用戶細節(確保主機是 localhost;本教程假定您是在 localhost 上測試),選擇? Create database with same name and grant all privileges ,如? 圖 1 ?所示。不要向您的 Openfire 數據庫新用戶授予全局數據庫特權。
圖 1. 在 phpMyAdmin 中添加一個 Openfire 數據庫?
添加用戶和數據庫后,就可以安裝 Openfire 服務器了。
安裝 Openfire
下載 Openfire 安裝程序并運行它,將 Openfire 安裝到您選擇的位置(參見? 參考資料 )。(您也可以選擇從 Openfire 的 Subversion 源代碼知識庫檢查 Openfire 的最新版本并本地構建它,但這個主題超出了本教程的范圍。)收到提示時,告知 Openfire 安裝程序在安裝完成時啟動服務器。
服務器啟動后,您應該看到服務器狀態窗口,如? 圖 2 ?所示:
單擊? Launch Admin ?打開一個基于 web 的向導,如? 圖 3 ?所示,該向導將帶您逐步配置您的 Openfire 服務器:
配置向導允許您選擇使用標準數據庫連接或嵌入式數據庫連接。選擇標準數據庫連接,以便您能夠使用您的 MySQL 數據庫。
從? Database Driver Presets ?列表選擇 MySQL。將您的服務器和數據庫名稱插入? Database URL ?字段。例如,對于在 localhost 上設置、名為 openfire 的 MySQL 數據庫而言,應輸入:
jdbc:mysql://localhost:3306/openfire |
在向導的下一屏幕上,選擇將用戶帳戶存儲在數據庫中。輸入此前創建的數據庫用戶的用戶名和密碼,然后一直繼續到配置向導結束。此時,您應該已經為您的 XMPP 服務器創建了一個服務器管理員并設置了域位置。
每用戶通知的插件
根據本教程的演示目的,您將只需使用在這里創建的兩個用戶。但是,如果您希望在您的應用程序中支持復雜的每用戶通知,那么您需要能夠以編程方式從您的應用程序的 PHP 部分添加和移除用戶。Openfire 的 User Service 插件通過一個用于 XMPP 用戶管理的 REST 界面向您提供這個功能。要安裝這個插件,從 Openfire 插件站點(參見? 參考資料 )下載它。插件本身是單個文件:userservice.jar。您必須將其放置到您的 Openfire 安裝的 /plugins 目錄中。
使用您建立的管理員憑證登錄到管理屏幕。單擊?
Edit Properties
(位于?
Server ports
?下方),記錄列示的服務器名稱。這個名稱將形成您的 JIDs 的域部分。這個名稱是不可互換的,比如,不能使用?
localhost
?替代?
127.0.0.1
,反之也不行。
單擊頂部導航菜單中的? Users/Groups ?并創建兩個新用戶。這些用戶將您在開發過程中的測試用戶。
單擊? Server settings ,然后單擊? Offline messages 。由于您將 XMPP 用于界面通知,因此應將? Offline Message Policy ?設置為? Drop ,如? 圖 4 ?所示。您不想保存用戶沒有登錄時收到的消息,否則,當他們返回時,可能會被數千條通知所 “淹沒”。
在? Server settings ?中,單擊? Server to Server 。對于本文,您不必連接到外部服務器,因為您不需要作為更大的 XMPP 網絡的一個連接部分操作。因此,將? Service Enabled ?設置為? Disabled , Allowed to Connect ?設置為? White List 。這些設置將阻止未授權的連接造成破壞。
配置 Apache 以通過 BOSH 轉發 XMPP
Openfire 在 http://localhost:7070/http-bind 維護了一個 HTTP 綁定 URL,以便通過 BOSH 訪問。要在端口 80 上使用這個 URL,您必須配置 Apache HTTP Server 以將一個 URL 轉發到這個位置。為此,您需要啟動代理模塊。
打開您的 http.conf Apache 配置文件并找到?
mod_proxy.so
和?
mod_proxy_http.so
?的?
LoadModule
?條目,它們默認被注釋掉。移除前導的井字符(
#
),取消注釋。這個配置文件的 Dynamic Shared Object (DSO) Support 部分中的多個適當的行(不一定在一起)現在應該類似于?
清單 8
:
清單 8.? 啟用 Apache HTTP Server? 中的代理支持
LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_module modules/mod_proxy.so |
在配置文件的末尾,添加? 清單 9 ?中的行(如果您沒有將 locahost 作為您的測試服務器環境,則應將 127.0.0.1 替換為您的服務器 IP 地址):
清單 9. httpd.cof? 中的 XMPP? 代理規則
# XMPP proxy rule ProxyRequests Off ProxyPass /xmpp-httpbind http://127.0.0.1:7070/http-bind/ ProxyPassReverse /xmpp-httpbind http://127.0.0.1:7070/http-bind/ |
注意,在?
清單 9
?中,您在端口 80 上使用了一個稍微不同的 URL:
/xmpp-httpbind
。這個 URL 是 strophe.js(您稍后將用到的客戶端 JavaScript 框架)分配給一個用于設置 BOSH 端點的變量的值。
重啟服務器。現在,您可以開始編寫使用 XMPP 的 web 應用程序了。
創建使用 XMPPHP 的服務器應用程序
在前面的小節中,您設置了服務器和插件。在本小節中,您將創建您的實時應用程序的服務器端部分。
服務器端功能
您的應用程序的 PHP 端將執行以下兩個主要任務:
1. 一個 PHP 腳本將獲取一個 RSS,并將在第一個條目自您上次檢查以來發生改變時通知您。
2. 一個前端腳本將初始化您的 JavaScript XMPP 客戶端。
首先在您的測試服務器的 web 根中創建一個名為 pingstream 的 PHP 新項目空間。如果您使用針對 Eclipse 開發環境的 PHP Development Tools 項目擴展,那么要注意,要在 web 根、而不是 Eclipse 的默認工作空間中創建這個項目(參見? 圖 5 ):
圖 5 ?顯示了這個 Pingstream 項目的以下值:
l Project name 字段:Pingstream
l Contents 單選按鈕:Create project from existing source
l Directory 字段:c:\xampp\htdocs\pingsstream
l PHP Version 單選按鈕:Use default PHP settings
XMPPHP 是應用最廣泛的針對 PHP 的 XMPP 庫。與 ejabberd 和 Openfire 一樣,它也是免費和開源的,因此它是首次使用 XMPP 開發人員的一個不錯的起點。
下載 XMPPHP(參見? 參考資料 )。解包這個歸檔并將其插入您的新項目的 lib/xmpphp 子文件夾中。
最后,為節約您的 RSS 提要的解析時間,下載并安裝 Last RSS PHP 解析器(參見? 參考資料 )。
存儲服務器端設置
您將使用此前創建的兩個測試用戶之一作為您的通知發送方。在您的 /pingstream 的根中創建一個名為 config.inc.php 的文件,并添加? 清單 10 ?中的內容:
<?php // Pingstream configuration file // Define global $CONFIG array - don't change this! global $CONFIG; $CONFIG = array(); $CONFIG['send'] = new stdClass(); // Set account details for sending party $CONFIG['send']->user = 'testuser'; // User portion of JID $CONFIG['send']->host = '127.0.0.1'; // Host portion of JID $CONFIG['send']->resc = 'pingstream';// Resource portion of JID $CONFIG['send']->pass = 'mypass'; // Password for user |
config.inc.php 文件還必須包含關于您的接收方的細節,如? 清單 11 ?所示:
// Set the receiving account details $CONFIG['receive'] = 'receivinguser@127.0.0.1'; |
您還需將少量數據緩存在一個文件中,因此 config.inc.php 需要包含一個可寫入的文件路徑,如? 清單 12 ?所示:
// Full path to your cache directory, with trailing slash $CONFIG['cachedir'] = '/tmp/'; |
如果您位于一臺 Microsoft? Windows? 機器上,那么您可以將這個位置設置為一個空字符串。否則,確保您指定的目錄包含一個完整路徑和一個結束斜杠,并被設置為全局可寫入(world-writeable)。
定義數據源
您的 Pingstream 應用程序將檢查 IBM developerWorks Web development 專區提要獲取更新,因此,將? 清單 13 ?中的內容添加到 config.inc.php:
清單 13. 檢查一個 developerWorks 提要獲取更新
// Set the RSS feed you're going to check $CONFIG['rss'] = 'http://www.ibm.com/developerworks/views/web/rss/libraryview.jsp'; |
現在,創建另一個新文件:/lib.inc.php。這將是您的庫文件,包含在您的應用程序的主控制器頁面和用戶界面頁面中。
lib.inc.php 的頂端必須引用 config.inc.php(主 XMPPHP 庫)和 lastRSS,如? 清單 14 ?所示:
<?php // Load libraries require_once('XMPPHP/XMPP.php'); require_once('config.inc.php'); require_once('lastRSS.php'); |
接下來,您將創建一個函數來通過 XMPP 將一條消息發送到客戶端。通過 XMPPHP 完成這個任務很簡單,只需使用在您的配置文件中保存的憑證建立一個連接,發送消息,然后關閉連接。
在這個函數的第一部分中,您創建了一個新的 XMPPHP_XMPP 對象,如? 清單 15 ?所示:
// Load configuration global $CONFIG; $conn = new XMPPHP_XMPP( $CONFIG['connect']->host, 5222, $CONFIG['connect']->user, $CONFIG['connect']->pass, $CONFIG['connect']->resc); |
注意,XMPPHP 通過 XMPP 通信的默認端口 5222 連接到您的 XMPP 服務器。盡管您的客戶端通信需要使用 BOSH,但服務器端沒有這個要求。
要連接到 XMPP 服務器,需要發送一個初始連接請求,一直等到接收到您的 XMPP 會話已經啟動的通知,然后發送一個聯機狀態節來聲明您處于在線狀態(參見? 清單 16 ):
$conn->connect(); $conn->processUntil('session_start'); $conn->presence(); |
下一步是發送消息本身,該消息已被預先填充到一個名為 $message 的變量中(參見? 清單 17 ):
$conn->message($CONFIG['receive'], $message); |
然后您使用 $conn->disconnect(); 斷開連接。
在此過程中,您可能會遇到錯誤。 清單 18 ?將清單? 15 、 16 ?和? 17 ?中的代碼放到一個將插入到 lib.inc.php 中的函數中。在這個過程中,它將發送一條消息的業務封裝到一個 try/catch 語句中,該語句將把任何異常消息寫入錯誤日志中。
清單 18. 完整的 send_notification 函數,包含 try/catch 語句以記錄錯誤
/** * Updates everyone's user interface with a message */ function send_notification($message) { // Load configuration global $CONFIG; $conn = new XMPPHP_XMPP( $CONFIG['connect']->host, 5222, $CONFIG['connect']->user, $CONFIG['connect']->pass, $CONFIG['connect']->resc); try { $conn->connect(); $conn->processUntil('session_start'); $conn->presence(); $conn->message($CONFIG['receive'], $message); $conn->disconnect(); } catch(XMPPHP_Exception $e) { error_log($e->getMessage()); } } |
您可以對任意數量的公共通知應用程序使用這個簡單的機制。這里,您向訪問站點的所有用戶發送相同的通知,但通過以下機制來定制通知是件麻煩事:
每個用戶被賦予一個惟一會話字符串,或者一個特定于某個特殊興趣或搜索的共享字符串;
這個字符串作為資源片段被添加到接收方的 JID;
消息然后被發送到 user@domain/session-string JID。
獲取外部數據
接下來,您需要一個函數(參見? 清單 19 )來從您的指定提要檢索最新的 RSS 條目:
function get_last_feed_item() { global $CONFIG; // Load configuration $rss = new lastRSS; // Initialize lastRSS $rss->CDATA = 'content'; if ($rs = $rss->get($CONFIG['rss'])) { if (isset($rs['items'][0])) return $rs['items'][0]; else return false; } } |
這個函數初始化 lastRSS,加載您配置的 RSS 提要(這里是 IBM developerWorks Web development 專區的最新文章提要),將最頂端的條目返回為一個數組。擁有這個最新提要條目后,您需要知道自從上次檢查以來,該條目是否被更改。為此,您需要使用一個小型文本文件。為保護應用程序,最好使用數據庫或另一種方法,但對于測試目的,可以放心使用一個小型文件緩存。這個緩存在您每次檢查時都存儲最新提要條目的 URL;如果這個 URL 更改,那么您就有一個新條目,應該通知用戶。另一個函數 feed_has_changed 將據此返回 true 或 false。不管是哪種情況,它都會將這個最新 URL 保存到緩存文件中,為下次檢查做好準備。 清單 20 ?展示了 feed_has_changed 函數:
function feed_has_changed($url) { global $CONFIG; $changed = false; // Check to see if the file exists, and if it does, if // the URL has changed if (!file_exists($CONFIG['cachedir'] . 'cache.txt')) { $changed = true; } else if (file_get_contents($CONFIG['cachedir'] . 'cache.txt') != $url) { $changed = true; } // If the URL has indeed changed, update the version in the cache // and return true if ($changed) { file_put_contents($CONFIG['cachedir'] . 'cache.txt', $url); return true; } // Otherwise return false return false; } |
您將把最新條目的一個簡單的 HTML 編碼版本傳遞給客戶端。在更高級的應用程序中,可以以 JavaScript Object Notation (JSON) 或 XML 編碼該條目,多包括一些元數據,并允許客戶端 JavaScript 根據設備和瀏覽器適當格式化它。但是,對于現在, 清單 21 ?中的版本就夠用了。
function last_item_html($item) { return <<< END <div class="item"> <div class="item_title"> <h2><a href="{$item['link']}" target="_blank">{$item['title']}</a></h2> </div> <div class="item_body"> {$item['description']} </div> </div> END; } |
最后,在您的 /pingstream 目錄中創建一個名為 backend.php 的新 PHP 文件。使用? 清單 22 ?中的簡單 PHP 腳本作為文件內容。這個腳本負責檢索 RSS 提要并通過 XMPP 將最新提要條目的 JSON 編碼版本發送到您的接收方。
<?php require_once('lib.inc.php'); if ($lastitem = get_last_feed_item()) { if (feed_has_changed($lastitem['link'])) { send_notification(last_item_html($lastitem)); } } |
這就是將動態通知發送給應用程序的公共用戶所需的全部內容。理想情況下,您應該將 backend.php 腳本作為一個定期時間任務運行。但是,對于測試目的,您可以通過一個 web 瀏覽器手動執行該腳本。
瀏覽器應用程序:Strophe.js 和 jQuery
在本小節中,您將編寫一些 JavaScript 函數,以便通過 BOSH 上的 XMPP 接收消息,并構建一個 HTML 用戶界面來顯示接收到的通知。
創建用戶界面
現在您需要創建用戶界面來接收通知。Strophe.js 是用于通過 BOSH 發送和接收 XMPP 數據的常用 JavaScript 庫。對于 Pingstream 中的目的,您只需接收數據,盡管有一點是顯而易見的:雙向通信允許您快速構建豐富的協作環境。
盡管有幾個版本,但 Strophe 的 JavaScript 版本作為一個基于瀏覽器的 XMPP 客戶端對您而言是最有用的。下載壓縮包(參見? 參考資料 )并將其解壓縮到 pingstream 的 strophejs 文件夾中。
jQuery JavaScript 框架極大地簡化了事件處理和 DOM 操作。本文提供的 Strophe.js 示例廣泛使用該框架,這兩者簡直是 “天生一對”。下載 jQuery(參見? 參考資料 ?中的鏈接)并將這個縮微版放到 pingstream 中的 jquery 文件夾中。
新建一個 index.html 文件。在該文件中包含對剛才下載的 Strophe 和 jQuery 庫的引用,以及對稍后即將定義的 pingstream.js 庫的引用。在?
body
?元素中,添加一個 ID 為 notifications 的?
div
?元素,如?
清單 23
?所示:
<!DOCTYPE html> <html> <head> <title>Latest content</title> <script type="text/javascript" src="jquery/jquery-1.4.2.min.js"></script> <script type="text/javascript" src="strophejs/strophe.js"></script> <script type="text/javascript" src="pingstream.js"></script> </head> <body> <h1>Latest content:</h1> <div id="notifications"></div> </body> </html> |
創建 JavaScript 文件 — pingstream.js — 您剛才在? 清單 23 ?中引用的。在 pingstream.js 的頂端,定義此前在 Apache 中配置的 BOSH 代理端點,如? 清單 24 ?所示:
var BOSH_SERVICE = '/xmpp-httpbind'; var connection = null; |
當頁面完全加載后,您想自動連接到 XMPP 服務器。您可以使用 jQuery 的?
$(document).ready
?調用實現這個目標;其中,您新建一個?
strophe.js Strophe.Connection
?對象并用它連接到服務器,如?
清單 25
?所示:
$(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.connect( "sendinguser@127.0.0.1", "sendingpass", onConnect); }); |
更健壯的選項
對于本教程的目的,您正在使用此前定義的發送方。對于一個更健壯的應用程序,更好的方法可能是為每個注冊應用程序用戶創建一個新用戶,并將每個用戶訂閱到一個 “發布-訂閱” 界面。或者,如果您將用戶名和密碼留空并將 XMPP 服務器配置為接受這種類型的連接,那么 Strophe.js 可以匿名登錄。在這些情況下,將針對每個匿名用戶動態創建一個 JID;這些 JID 必須受到管理。最后,您還可以擴展 XMPP 聊天室。
在?
清單 25
?中,
Strophe.Connection.connect
?方法包含一個對?
onConnect
?函數的引用,作為它的一個參數。
onConnect
?將在連接建立后立即啟動。您可以利用這個機會來為入向消息添加一個通知處理程序;您在這里注冊了一個名為?
notifyUser
?函數。隨后,您發送了一個簡單的聯機狀態節。
要確保您可以連接并接收新消息,您還需向用戶發送一個友好通知。
將?
清單 26
?中的代碼添加到您的 JavaScript 文件中的?
$(document)ready
?調用上方:
function onConnect(status) { $('#notifications').html('<p class="welcome">Hello! Any new posts will appear below.</p>'); connection.addHandler(notifyUser, null, 'message', null, null, null); connection.send($pres().tree()); } |
最后,由于您注冊了通知處理程序,因此,只要 XMPP 客戶端接收到消息節,Strophe.js 就會調用?
notifyUser(msg)
?函數。
msg
?參數是 XML 節本身的一個表示,可以如?
清單 27
?所示查詢:
var elems = msg.getElementsByTagName('body'); var body = elems[0]; $('#notifications').append(Strophe.getText(body)); |
理想情況下,您希望對消息進行限制,以便只顯示您的服務器端發送用戶發送的消息。您可以將它封裝到構成?
notifyUser
?函數主體的一個?
if
?語句中,如?
清單 28
?所示:
function notifyUser(msg) { if (msg.getAttribute('from') == "testuser@127.0.0.1/pingstream") { var elems = msg.getElementsByTagName('body'); var body = elems[0]; $('#notifications').append(Strophe.getText(body)); } return true; } |
這個函數應位于在?
清單 26
?中定義的?
onConnect
?函數上方。
最終效果
在一個 web 瀏覽器中打開您的 index.html 文件。您應該會看到一個簡單的標題和一條消息,該消息稱更新將在下面顯示(這可能會使您回想起您發給自己的測試通知,稱 XMPP 連接正在成功運行)。
現在加載 backend.php。就像變戲法一樣,來自 IBM developerWorks Web development 專區的最新更新將顯示在您的頁面上。其他帶有 RSS 提要的示例源包括 Twitter 帳戶、通訊社、以及來自服務器監控軟甲的更新提要。
這是開發一個強大平臺的簡單起點。Strophe.js 能夠促進應用程序的雙向通信,盡管更簡單的方法是使用標準的 jQuery HTTP 回撥來將用戶輸入送入系統,從而避免為您的應用程序編寫一個 XMPP 后臺監控進程的麻煩。更令人興奮的是,當您 web 服務器用作 BOSH 代理時,完全無需太多來自服務器端 web 應用程序的輸入,兩個或更多 web 客戶端就能通過 XMPP 相互通信。這種技術將對從辦公室協作軟件到游戲的很多軟件產生深遠影響。
結束語
本教程討論了實時 web 應用程序的必要性,以及 XMPP 如何克服現有技術的缺點。為展示這種方法的效果,您使用 XMPPHP、Last RSS、Strophe.js、Openfire 和 PHP 開發了一個簡單的 RSS 更新通知應用程序。
盡管需要一個附加服務器層和一些 JavaScript 新技術,但 XMPP 比傳統 Ajax 輪詢模型更加適合實時 web 應用程序。XMPP 更快,在開發和系統基礎設置方面需要的開銷更少,并且使用一個強大的新興 web 開發標準。
下載
描述 |
名字 |
大小 |
下載方法 |
Pingstream 源代碼 |
pingstream.zip |
238KB |
參考資料
學習
l 您可以參閱本文在 developerWorks 全球網站上的? 英文原文 。
l? XMPP Standards Foundation :訪問 XMPP 官方站點。
l? 實現可擴展消息傳遞和到場協議(XMPP) (M. Tim Jones,developerWorks,2009 年 9 月):探索 XMPP 的細節,了解如何將它用于簡單消息傳遞。
l? 使用 XMPP、SMS、pureXML 和 PHP 創建警報系統 (Joe Lennon,developerWorks,2009 年 11 月):嘗試使用 XMPP 來將通知發送到 Google Talk。
l? Jabber (Gerhard Poul,developerWorks,2002 年 5 月):查看這個早期 XMPP 簡介。
l? Ajax 和 XML: 將 Ajax 用于聊天 (Jack D Herrington,developerWorks,2007 年 12 月):了解如何使用 Ajax 輪詢實現一個實時 web 應用程序。
l? JavaScript Tutorial :了解如何使用這個 web 腳本語言。
l? Ejabberd :進一步了解這個用 Erlang 語言編寫的 XMPP 服務器。
l? My developerWorks: 個性化您的 developerWorks 體驗。
l? IBM XML 認證 :了解如何才能成為一名 IBM 認證的 XML 和相關技術的開發人員。
l? XML 技術庫 :訪問 developerWorks XML 專區,獲得廣泛的技術文章和技巧、教程、標準和 IBM 紅皮書。
l developerWorks? 技術活動 ?和? 網絡廣播 :隨時關注這些活動中的技術。
l? developerWorks 播客 :收聽面向軟件開發人員的有趣訪談和討論。
獲得產品和技術
l? Openfire :下載 Openfire,這是一個基于 XMPP (Jabber) 協議的跨平臺實時協作服務器。
l? PHP :訪問這個 PHP 站點,獲取這個應用廣泛的腳本語言,該語言非常適合 Web 開發,可以嵌入到 HTML 中。本教程使用 PHP 5.2 或更高版本。
l? Apache HTTP Server :下載這個 Apache web 服務器。
l? MySQL :下載這個開源事務型數據庫。
l? DB2 Express-C :下載這個免費版 IBM DB2 數據庫服務器,它是中小型企業應用程序開發的一個堅實基礎。
l? Openfire 插件 :獲取用于 Openfire 的 User Service 插件。
l? XMPPHP :從這個項目的 Google Code 站點下載 PHP XMPP Library。
l? Last RSS :獲取 Vojtech Semecky 針對 PHP 的 RSS 解析器(經過 GNU Public License version 2 許可)。
l? jQuery :下載 jQuery JavaScript 庫,經過 MIT 或 GNU Public License 許可。
l? Strophe :下載 Strophe.js,這是一系列用于編寫 XMPP 客戶端的庫。它的許可允許對其進行自由使用、修改和共享。
l? phpMyAdmin :獲取這個免費軟件工具,它支持通過 web 管理 MySQL。
l? IBM 產品評估試用版軟件 :下載或? 在線試用 IBM SOA Sandbox ,并開始使用來自 DB2?、Lotus?、Rational?、Tivoli? 和 WebSphere? 的應用程序開發工具和中間件產品。
討論
l? XMPP Discussion :訂閱面向開發人員、系統管理員和用戶的 XMPP 討論。
l? XML 專區討論論壇 :參與任何一個 XML 相關討論。
l? developerWorks 博客 :閱讀這些博客并參與討論。
?
引用:
http://www.cnblogs.com/alex-blog/articles/2665665.html
?
?
補充:如果相關的環境都配置成功時,使用xmpp.php庫進行相關的消息發送如果出現如下的錯誤,那么就是您的服務器上面的ssl沒有配置成功,具體的一個配置方法如下所示:
意思是不支持ssl,輸出phpinfo,發現自己的php環境沒有安裝ssl擴展,有些默認安裝了,有些沒有安裝
這里寫下windows下的安裝方法
1、拷貝PHP 目錄中的libeay32.dll, ssleay32.dll, php5ts.dll, php ext 目錄下 php_curl.dll 文件到
system32 目錄。(一定要拷貝全啊,不要丟掉哪個,不然會報錯的)
2、修改php.ini:配置好extension_dir ,去掉extension = php_curl.dll?
3、重起apache。
再次輸出phpinfo,看到如下內容就安裝成功了
?這晨需要注意一下的是,您需要確認你所更改的php.ini是否是真正的配置文件,我上回就碰到了一個問題。在php的目錄下面,確實是存在一個Php.ini的文件,我改完相磁的配置以后,完全沒有反應,依然報如上的錯誤。后面我使用phpinfo輸出一看才明白,其輸出的結果php.ini指向的文件并不是這個實際文件,而是另外的一名字,而后,我在那個文件中進行了相應的修改。結果可以正常的運行。在此提醒一下大家。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
