使用 XMPP 構(gòu)建一個(gè)基于 web 的通知工具 轉(zhuǎn)
使用 XMPP 構(gòu)建一個(gè)基于 web 的通知工具
使用 XMPP、PHP 和 JavaScript 編寫實(shí)時(shí) web 應(yīng)用程序
Ben Werdmuller , 顧問(wèn)和作者, Freelance
Ben Werdmuller 是一位 Web 策劃師和開(kāi)發(fā)人員,他專注于開(kāi)放源碼平臺(tái)。他是開(kāi)源社交網(wǎng)絡(luò)框架 Elgg 的共同創(chuàng)始人和技術(shù)帶頭人。Ben 的博客? http://benwerd.com/ 。
簡(jiǎn)介: ?實(shí)時(shí) web 應(yīng)用程序是聯(lián)網(wǎng)的應(yīng)用程序,帶有基于 web 的用戶界面,能夠及時(shí)顯示剛剛發(fā)布的 Internet 信息。這樣的應(yīng)用程序示例包括社會(huì)新聞聚合器和監(jiān)控工具,它們能夠使用來(lái)自外部源的數(shù)據(jù)持續(xù)更新。在本教程中,您將創(chuàng)建一個(gè)小型通知工具 Pingstream,它使用 PHP 和 JavaScript 通過(guò) Extensible Messaging and Presence Protocol (XMPP) 進(jìn)行通信,XMPP 是一組設(shè)計(jì)用于支持聯(lián)機(jī)狀態(tài)和實(shí)時(shí)通信功能的 XML 技術(shù)。
開(kāi)始之前
本教程將向您介紹實(shí)時(shí) web,并詳細(xì)介紹之所以要構(gòu)建實(shí)時(shí) web 應(yīng)用程序的幾個(gè)原因。您將學(xué)到一些技術(shù),這些技術(shù)將幫助您創(chuàng)建響應(yīng)及時(shí)、持續(xù)更新的 web 應(yīng)用程序,這些應(yīng)用程序既能保護(hù)服務(wù)器資源,又能提供良好的用戶體驗(yàn)。
關(guān)于本教程
常用縮略詞
DOM: 文檔對(duì)象模型
HTML: 超文本標(biāo)記語(yǔ)言
HTTP: 超文本傳輸協(xié)議
REST: 具象狀態(tài)傳輸
RSS: 真正簡(jiǎn)單聚合
URL: 統(tǒng)一資源定位符
XML: 可擴(kuò)展標(biāo)記語(yǔ)言
實(shí)時(shí) web 應(yīng)用程序允許用戶在信息發(fā)布時(shí)及時(shí)接收通知,無(wú)需手動(dòng)檢查原始源獲取更新。通過(guò) Twitter 和 Friendfeed 這樣的社交通知工具,Google Wave 這樣的基于 web 的協(xié)作工具,以及 Meebo 這樣的基于 web 的聊天客戶端,實(shí)時(shí) web 應(yīng)用程序逐漸流行起來(lái)。
Extensible Messaging and Presence Protocol (XMPP) 是一組基于 XML 的技術(shù),用于實(shí)時(shí)應(yīng)用程序,定義為持續(xù)更新以響應(yīng)新數(shù)據(jù)或更改數(shù)據(jù)的聯(lián)網(wǎng)應(yīng)用程序。它最初作為一個(gè)框架研發(fā),以支持企業(yè)環(huán)境內(nèi)的實(shí)時(shí)消息傳遞和聯(lián)機(jī)狀態(tài)(presence)應(yīng)用程序。
在本教程中,您將構(gòu)建一個(gè)簡(jiǎn)單的工具 Pingstream,它在 RSS 提要更新發(fā)布時(shí)使用它們持續(xù)更新自身(參見(jiàn)? 下載 ?獲取 Pingstream 源代碼)。在此過(guò)程中,您將:
了解 XMPP 為何特別適合 web 應(yīng)用程序;
了解 XMPP 通信的組件;
安裝和配置 Openfire XMPP 服務(wù)器;
使用 PHP 和 XMPPHP 庫(kù)連接到 XMPP 服務(wù)器;
檢查并通過(guò) XMPP 傳輸 RSS 提要中的新項(xiàng)目;
借助 Bidirectional-streams Over Synchronous HTTP (BOSH),使用 Strophe 和 jQuery 通過(guò) HTTP 連接到 XMPP 服務(wù)器;
在 web 頁(yè)面中顯示 XMPP 通知。
先決條件
本教程假定您比較熟悉使用 PHP 開(kāi)發(fā) web 應(yīng)用程序,但也會(huì)涉及一些高級(jí)編程方法。您還應(yīng)該擁有一定的 HTML 和 JavaScript 經(jīng)驗(yàn)。擁有 jQuery JavaScript 框架經(jīng)驗(yàn)可能會(huì)有所幫助。但本教程不要求熟悉 XMPP 或類似的技術(shù)。
要跟隨本教程操作,必須安裝和運(yùn)行以下服務(wù)器軟件:
PHP 5.2 或更高版本
Apache HTTP Server
MySQL
在本教程中,您還將下載和安裝以下軟件和庫(kù):
Openfire
jQuery
Strophe
XMPPHP
Last RSS
您可能會(huì)發(fā)現(xiàn) MySQL 服務(wù)器工具 phpMyAdmin 能夠派上用場(chǎng)。如果您使用一臺(tái)桌面機(jī)器來(lái)本地測(cè)試您的實(shí)時(shí) web 應(yīng)用程序,您可能會(huì)發(fā)現(xiàn) XAMPP 對(duì)于管理一個(gè)測(cè)試 web 服務(wù)器基礎(chǔ)設(shè)施的安裝和運(yùn)行很有用。
實(shí)時(shí) web 簡(jiǎn)介
在本小節(jié)中,您將了解什么是實(shí)時(shí) web 應(yīng)用程序,您為何可能需要構(gòu)建這樣的應(yīng)用程序,以及它們與典型的現(xiàn)代 web 應(yīng)用程序模型的區(qū)別何在。
接受輸入并提供反饋
實(shí)時(shí) web 并不實(shí)時(shí)
持續(xù)更新的 ?web? 應(yīng)用程序 ?這個(gè)詞匯比 實(shí)時(shí) ?web? 應(yīng)用程序 ?更貼切。在計(jì)算機(jī)科學(xué)中, 硬 ?和 軟 ?實(shí)時(shí)系統(tǒng)都必須滿足操作期限的要求。當(dāng)任務(wù)不能在其分配時(shí)間內(nèi)完成時(shí),硬實(shí)時(shí)系統(tǒng)將失敗。實(shí)時(shí) web 應(yīng)用程序與軟實(shí)時(shí)系統(tǒng)更相似,在軟實(shí)時(shí)系統(tǒng)中,某個(gè)功能的延遲并不會(huì)導(dǎo)致系統(tǒng)失?。ǖ赡軙?huì)降低性能)。但您不應(yīng)對(duì)實(shí)時(shí) web 應(yīng)用程序分配嚴(yán)格的任務(wù)計(jì)劃。目前,web 并不是適合時(shí)間關(guān)鍵型應(yīng)用程序的平臺(tái)。
應(yīng)用程序是幫助用戶執(zhí)行任務(wù)的專門軟件,其特征是:從用戶或其他源接收輸入,然后提供可讀的輸出。應(yīng)用程序也可能動(dòng)態(tài)響應(yīng) — 以可視或編程方式 — 自動(dòng)接收的輸入數(shù)據(jù)中的變化。例如,當(dāng)包含特定關(guān)鍵字的新聞出現(xiàn)在一個(gè)新聞監(jiān)控應(yīng)用程序連接到的新聞專線中時(shí),該應(yīng)用程序可能會(huì)通知用戶。
由于 web 的起源是作為一個(gè)文檔服務(wù)平臺(tái),因此它沒(méi)有針對(duì)應(yīng)用程序優(yōu)化。HTML 非常適合表示和超鏈接文本內(nèi)容,但不適合創(chuàng)建動(dòng)態(tài)界面。web 應(yīng)用程序能夠接收和響應(yīng)用戶輸入,這要?dú)w功于 PHP 這樣的服務(wù)器端腳本語(yǔ)言與表單和 JavaScript 這樣的 web 輸入技術(shù)的結(jié)合。但是,要?jiǎng)?chuàng)建 自動(dòng) ?更新的界面,您必須克服一些障礙。這些障礙比較難以克服,因?yàn)闆](méi)有任何 web 技術(shù)在研發(fā)時(shí)考慮到這個(gè)功能。相比之下(以新聞監(jiān)控為例),桌名軟件無(wú)需刷新其界面就能夠?qū)⑼ㄖl(fā)送給用戶;桌面軟件可以持續(xù)更新自身。相反,web 則局限于基于頁(yè)面的模型。
然而,基于 web 的實(shí)時(shí)應(yīng)用程序還是可以實(shí)現(xiàn)的,而且它們的好處顯而易見(jiàn)。這樣的應(yīng)用程序包括企業(yè)聊天工具、聯(lián)網(wǎng)的實(shí)時(shí)文檔協(xié)作工具、以及搜索界面,它們能夠及時(shí)顯示新發(fā)布的內(nèi)容。
面向健壯的應(yīng)用程序開(kāi)發(fā)的 Web 技術(shù)
通常,web 應(yīng)用程序通過(guò)使用 Asynchronous JavaScript and XML (Ajax) 工具來(lái)模擬持續(xù)更新的界面。在這個(gè)模型中,應(yīng)用程序的 web 頁(yè)面中包含 JavaScript,它在后臺(tái)反復(fù)請(qǐng)求一個(gè)服務(wù)器回?fù)?。盡管 Ajax 應(yīng)用程序的響應(yīng)性在很多情況下都?jí)蛴?,但這種技術(shù)還是有一些缺陷。
Ajax 不能容忍不穩(wěn)定的 Internet 連接:一次臨時(shí)掉線可能會(huì)導(dǎo)致整個(gè)界面失敗。它在服務(wù)器負(fù)載方面也效率低下。假設(shè)您的后臺(tái) Ajax 輪詢函數(shù)每 10 秒檢查一次服務(wù)器。每一次都將建立一個(gè)新的 HTTP 連接,包括初始化服務(wù)請(qǐng)求所需的資源,即使沒(méi)有新數(shù)據(jù)可以向用戶顯示。結(jié)果是應(yīng)用程序不必要地使用過(guò)多的處理器時(shí)間和帶寬。
基于 XML 的技術(shù)向 web 應(yīng)用程序提供了巨大的優(yōu)勢(shì)。XML 解析器現(xiàn)在是大多數(shù)環(huán)境的一個(gè)標(biāo)準(zhǔn)組成部分;無(wú)需其他軟件就能支持以適當(dāng)?shù)母袷阶x寫數(shù)據(jù)。XML 是自我描述型語(yǔ)言;使用它的文檔不需要外部架構(gòu)。最后,正如 web 是獨(dú)立于平臺(tái)的一樣,XML 作為一種技術(shù)支持在不同平臺(tái)間互操作。因此,開(kāi)發(fā)人員可以將精力集中于特定于他們的應(yīng)用程序的邏輯。
web 基于 HTML、Cascading Style Sheets (CSS) 和 JavaScript 等可互操作的免費(fèi)開(kāi)源標(biāo)準(zhǔn)。如果針對(duì) web 上的實(shí)時(shí)通信的新標(biāo)準(zhǔn)出現(xiàn),那么該標(biāo)準(zhǔn)也應(yīng)是免費(fèi)、開(kāi)源和可互操作的。基于 XML 的 XMPP 滿足這些標(biāo)準(zhǔn)。在本教程中,您將使用 XMPP 來(lái)構(gòu)建一個(gè)客戶端庫(kù),它通過(guò)標(biāo)準(zhǔn)方法(比如一個(gè) web hook)來(lái)接收輸入,并將適當(dāng)?shù)臄?shù)據(jù)實(shí)時(shí)中繼到用戶。
XMPP 簡(jiǎn)介
本小節(jié)將簡(jiǎn)要介紹 XMPP,它的起源,以及為何它是一個(gè)適合實(shí)時(shí) web 通信的協(xié)議。您將檢查 XMPP 通信設(shè)置的組件,并查看展示這些組件如何使用的示例。
Web 標(biāo)準(zhǔn)和 XMPP
XMPP 是一組基于 XML 的技術(shù),用于實(shí)時(shí)應(yīng)用程序。最初,XMPP 作為一個(gè)框架開(kāi)發(fā),目標(biāo)是支持企業(yè)環(huán)境內(nèi)的即時(shí)消息傳遞和聯(lián)機(jī)狀態(tài)應(yīng)用程序。當(dāng)時(shí)的即時(shí)消息傳遞網(wǎng)絡(luò)是私有的,非常不適合企業(yè)使用。例如,AOL Instant Messenger 不能針對(duì)公司內(nèi)的安全通信進(jìn)行調(diào)整。盡管存在一些商業(yè)解決方案,但它們固定的特性集通常不能進(jìn)行調(diào)整,以滿足組織的特殊需求。XMPP,當(dāng)時(shí)名為 Jabber,允許組織構(gòu)建自己的定制工具來(lái)促進(jìn)實(shí)時(shí)通信,并允許安裝現(xiàn)成的第三方解決方案。
XMPP 是一個(gè)分散型通信網(wǎng)絡(luò),這意味著,只要網(wǎng)絡(luò)基礎(chǔ)設(shè)施允許,任何 XMPP 用戶都可以向其他任何 XMPP 用戶傳遞消息。多個(gè) XMPP 服務(wù)器也可以通過(guò)一個(gè)專門的 “服務(wù)器-服務(wù)器” 協(xié)議相互通信,提供了創(chuàng)建分散型社交網(wǎng)絡(luò)和協(xié)作框架的有趣可能性,但這個(gè)主題已超出了本教程的討論范圍。
顧名思義,XMPP 可用于滿足廣泛的、對(duì)時(shí)間敏感的特性要求。實(shí)際上,Google Wave,一個(gè)大型多用戶協(xié)作環(huán)境,將 XMPP 作為其聯(lián)合協(xié)議的基礎(chǔ)。盡管 XMPP 的出現(xiàn)是為了滿足 “個(gè)人-個(gè)人” 即時(shí)消息傳遞的要求,但它完全不必局限于此任務(wù)。
XMPP 通信的結(jié)構(gòu)
要促進(jìn)消息傳遞,每個(gè) XMPP 客戶端用戶必須擁有一個(gè)全局惟一標(biāo)識(shí)符?;跉v史原因,這些標(biāo)識(shí)符稱為 Jabber IDs,或稱為 JIDs。鑒于這個(gè)協(xié)議的分布式特征,重要的是 JID 應(yīng)包含聯(lián)系用戶所需的所有信息:不存在將用戶鏈接到他們連接到的服務(wù)器的中央知識(shí)庫(kù)。JID 的結(jié)構(gòu)類似于電子郵件地址(但不要求 JID 同時(shí)也是有效的電子郵件收件人)。
客戶端和服務(wù)器節(jié)點(diǎn),我將它們統(tǒng)稱為? XMPP? 實(shí)體 ,都擁有 JIDs。SomeCorp 公司的員工 John Doe 可能擁有 JID John.Doe@somecorp.com。這里,somecorp.com 是 SomeCorp 公司的 XMPP 服務(wù)器的地址,John.Doe 是 John Doe 的用戶名。
JIDs 還擁有連接到它們的資源。這允許在一個(gè) XMPP 實(shí)體標(biāo)識(shí)符之外進(jìn)一步處理細(xì)粒度;例如,盡管上面的示例總體上能夠表示 John Doe,但 John.Doe@somecorp.com/Work 可以用于將數(shù)據(jù)發(fā)送到與他的工作相關(guān)的工具。
這些資源可以采用任意用戶定義的名稱,一個(gè) XMPP 實(shí)體可以擁有任意數(shù)量的資源。除了可以是上下文依賴的外,它們還可以綁定到設(shè)備、工具或工作站。對(duì)于您的 Pingstream 示例,web 站點(diǎn)的每個(gè)訪問(wèn)者都將作為同一個(gè)用戶登錄 XMPP 服務(wù)器,但他們擁有不同的資源。
通信類別
使用 XMPP 的實(shí)時(shí)消息傳遞系統(tǒng)包含三大通信類別:
消息傳遞,其中數(shù)據(jù)在有關(guān)各方之間傳輸;
聯(lián)機(jī)狀態(tài),它允許用戶廣播其在線狀態(tài)和可用性;
信息/查詢請(qǐng)求,它允許 XMPP 實(shí)體發(fā)起請(qǐng)求并從另一個(gè)實(shí)體接收響應(yīng)。
這些類別是互補(bǔ)的。例如,如果用戶或?qū)嶓w離線(盡管在許多用例中,理想的狀態(tài)是服務(wù)器在用戶返回之前一直持有用戶的消息),則沒(méi)有將數(shù)據(jù)發(fā)送給用戶或發(fā)起一個(gè)實(shí)體的信息/查詢請(qǐng)求的點(diǎn)。這些消息中的每一條都將通過(guò)一個(gè)完整的 XML? 節(jié) ?傳遞 — XML 節(jié)是以 XML 表達(dá)的獨(dú)立信息項(xiàng)。
這三種類型的 XMPP 節(jié)都擁有以下公共屬性:
from:源 XMPP 實(shí)體的 JID;
to:目標(biāo)接收者的 JID;
id:這次對(duì)話的可選標(biāo)識(shí)符;
type:節(jié)的可選子類型;
xml:lang:如果內(nèi)容是人們可讀的,則為消息語(yǔ)言的描述。
基于 XMPP 的數(shù)據(jù)傳輸發(fā)生在一些 XML 流上,默認(rèn)在端口 5222 上操作。這些 XML 流實(shí)際上是兩個(gè)完整的 XML 文檔,每個(gè)文檔對(duì)應(yīng)一個(gè)通信方向。一旦會(huì)話建立,stream 元素將打開(kāi)。這個(gè)元素將封裝整個(gè)通信文檔。然后,一些節(jié)被注入這個(gè)文檔的第二層。最后,一旦通信結(jié)束,stream 元素將關(guān)閉,形成一個(gè)完整的文檔。
例如, 清單 1 ?展示了一個(gè) stream 元素,它建立了從客戶端到服務(wù)器的通信。
清單 1.? 建立從客戶端到服務(wù)器的通信的 stream? 標(biāo)記
<stream:stream from="[server]" id="[unique ID over conversation]" xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> |
消息
一旦通信建立,客戶端就能使用 message 元素將消息發(fā)送到另一個(gè)用戶,message 元素包含以下任意子元素:
subject:一個(gè)可讀的字符串,表示消息主題。
body:一個(gè)可讀的字符串,表示消息體。如果每個(gè)消息體標(biāo)記都擁有一個(gè)不同的 xml:lang 值,那么可以包含多個(gè)消息體標(biāo)記。(xml:lang 是惟一可能的屬性。)
thread:一個(gè)惟一標(biāo)識(shí)符,表示一個(gè)消息線程??蛻舳塑浖梢允褂眠@個(gè)子元素將相關(guān)消息串聯(lián)在一起。
但是,消息也可以非常簡(jiǎn)單,如? 清單 2 ?所示:
<message from="sendinguser@somedomain" to="recipient@somedomain" xml:lang='en'> <body> Body of message </body> </message> |
對(duì)于提供實(shí)時(shí) web 界面而言,消息節(jié)是最有用的節(jié)。“發(fā)布-訂閱” 模型 — 在實(shí)時(shí) web 應(yīng)用程序中使用消息來(lái)傳輸數(shù)據(jù)的一種替代方法 — 將稍后介紹。
信息/查詢
信息/查詢節(jié)擁有廣泛的功能。一個(gè)例子就是 “發(fā)布-訂閱” 模型,在該模型中,發(fā)布者通知服務(wù)器某個(gè)特定資源進(jìn)行了更新,服務(wù)器則通知已選擇訂閱這些通知并擁有適當(dāng)授權(quán)的所有 XMPP 用戶。
來(lái)自發(fā)布者的一系列項(xiàng)目被編碼為一些節(jié),格式為基于 XML 的 Atom 發(fā)布格式。每個(gè)項(xiàng)目都包含在一個(gè) item 元素內(nèi),然后合并到一個(gè) pubsub 元素中,最后成為一個(gè)信息/查詢節(jié)。在? 清單 3 (選自 XMPP 發(fā)布-訂閱規(guī)范)中,Shakespeare's Hamlet(JID 為 hamlet@denmark.lit/blogbot)用他著名的獨(dú)白發(fā)布一個(gè)更新到 pubsub.shakespeare.lit pubsub 更新節(jié)點(diǎn):
清單 3.? 對(duì) pubsub.shakespeare.lit pubsub? 更新節(jié)點(diǎn)的更新
<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> |
信息/查詢節(jié)也用于請(qǐng)求一個(gè)特定 XMPP 實(shí)體的有關(guān)信息。例如,在? 清單 4 ?中的節(jié)中,boreduser@somewhere 正在查找 friendlyuser@somewhereelse 擁有的公共項(xiàng)目。
清單 4.? 用戶查找由 friendlyuser@somewhereelse? 擁有的公共項(xiàng)目
<iq type="get" from="boreduser@somewhere" to="friendlyuser@somewhereelse" id="publicStuff"> <query xmlns="http://jabber.org/protocol/disco#items"/> </iq> |
反過(guò)來(lái),friendlyuser@somewhereelse 使用一列可被訂閱到使用 “發(fā)布-訂閱” 的項(xiàng)目進(jìn)行響應(yīng),如? 清單 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 ?中的信息/查詢節(jié)中的每個(gè)返回項(xiàng)目都擁有一個(gè)可以訂閱到的 JID。信息/查詢還允許超出本教程范圍的廣泛的服務(wù)器信息請(qǐng)求。它們中的許多在針對(duì)多服務(wù)器環(huán)境的 web 應(yīng)用程序上下文中有用,或者作為復(fù)雜的分散型協(xié)作框架的基礎(chǔ)。
聯(lián)機(jī)狀態(tài)
聯(lián)機(jī)狀態(tài)信息包含在一個(gè)聯(lián)機(jī)狀態(tài)(presence)節(jié)中。如果 type 屬性省略,那么 XMPP 客戶端應(yīng)用程序假定用戶在線且可用。否則,type 可設(shè)置為 unavailable,或者特定于 pubsub 的值:subscribe、subscribed、unsubscribe 和 unsubscribed。它也可以是針對(duì)另一個(gè)用戶的聯(lián)機(jī)狀態(tài)信息的一個(gè)錯(cuò)誤或探針。
一個(gè)聯(lián)機(jī)狀態(tài)節(jié)可以包含以下子元素:
show:一個(gè)機(jī)器可讀的值,表示要顯示的在線狀態(tài)的總體類別。這可以是 away(暫時(shí)離開(kāi))、chat(可用且有興趣交流)、dnd(請(qǐng)勿打擾)、或 xa(長(zhǎng)時(shí)間離開(kāi))。
status:一個(gè)可讀的 show 值。該值為用戶可定義的字符串。
priority:一個(gè)位于 -128 到 127 之間的值,定義消息路由到用戶的優(yōu)先順序。如果值為負(fù)數(shù),用戶的消息將被扣留。
例如, 清單 6 ?中的 boreduser@somewhere 可以用這個(gè)節(jié)來(lái)表明聊天意愿:
清單 6.?
樣例聯(lián)機(jī)狀態(tài)通知
<presence xml:lang="en"> <show>chat</show> <status>Bored out of my mind</status> <priority>1</priority> </presence> |
注意 from 屬性此處省略。
另一個(gè)用戶 friendlyuser@somewhereelse 可以通過(guò)發(fā)送? 清單 7 ?中的節(jié)來(lái)探測(cè) boreduser@somewhere 的狀態(tài):
<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> |
這些聯(lián)機(jī)狀態(tài)值源自 “個(gè)人-個(gè)人” 消息傳遞軟件。show 元素的值 — 通常用于確定將向其他用戶顯示的狀態(tài)圖標(biāo) — 在聊天應(yīng)用程序之外如何使用現(xiàn)在還不清楚。狀態(tài)值可能會(huì)在微博工具中找到用武之地;例如,Google Talk(一個(gè) XMPP 聊天服務(wù))中的用戶狀態(tài)字段的更改可以被導(dǎo)入為 Google Buzz 中的微博條目。
另一種可能性就是將狀態(tài)值用作每用戶應(yīng)用程序狀態(tài)數(shù)據(jù)的攜帶者。盡管此規(guī)范將狀態(tài)定義為可讀,但沒(méi)有什么能夠阻止您在那里存儲(chǔ)任意字符串來(lái)滿足您的要求。對(duì)于某些應(yīng)用程序而言,它可以不是可讀的,或者,它可以攜帶微格式形態(tài)的數(shù)據(jù)負(fù)載。
您可以為一個(gè) XMPP 實(shí)體擁有的每個(gè)資源獨(dú)立設(shè)置聯(lián)機(jī)狀態(tài)信息,以便訪問(wèn)和接收連接到一個(gè)應(yīng)用程序中的單個(gè)用戶的所有工具和上下文的數(shù)據(jù)只需一個(gè)用戶帳戶。每個(gè)資源都可以被分配一個(gè)獨(dú)立的優(yōu)先級(jí);XMPP 服務(wù)器將首先嘗試將消息傳遞給優(yōu)先級(jí)較高的資源。
XMPP 使用 BOSH 越過(guò) HTTP
要通過(guò)使用 JavaScript 的 XMPP 進(jìn)行通信的 web 應(yīng)用程序必須符合一些特殊要求。出于安全考慮,不允許 JavaScript 從 web 頁(yè)面的域與不同域上的多個(gè)服務(wù)器通信。如果您的 web 應(yīng)用程序界面被托管在 application.mydomain.com,所有 XMPP 通信也必須發(fā)生在 application.mydomain.com。
防火墻是另一個(gè)問(wèn)題所在。理想情況下,如果您將 XMPP 用作您的 web 界面的實(shí)時(shí)元素的基礎(chǔ),那么您希望它對(duì)防火墻后面的用戶有效。但是,公司防火墻通常只對(duì)少數(shù)幾個(gè)協(xié)議開(kāi)放幾個(gè)端口,以便允許 web 數(shù)據(jù)、電子郵件和類似的通信通過(guò)。默認(rèn)情況下,XMPP 使用端口 5222,這很可能是公司防火墻阻止的端口。
假設(shè)您知道您的用戶前面的防火墻在端口 80 上允許 HTTP(這是用于訪問(wèn) web 的默認(rèn)協(xié)議和端口)。理想情況是您的 XMPP 通信能夠越過(guò)該端口上的 HTTP。但是,HTTP 的設(shè)計(jì)并不針對(duì)持續(xù)連接。web 的架構(gòu)不同于實(shí)時(shí)數(shù)據(jù)所需的通信架構(gòu)。
下面我們看看 Bidirectional-streams Over Synchronous HTTP (BOSH) 的標(biāo)準(zhǔn),該標(biāo)準(zhǔn)為雙向同步數(shù)據(jù)提供一個(gè)模擬層。借助這個(gè)標(biāo)準(zhǔn),可以與一個(gè) XMPP 服務(wù)器建立一個(gè)較長(zhǎng)的 HTTP 連接(時(shí)長(zhǎng)一分鐘或兩分鐘)。如果新數(shù)據(jù)在那個(gè)期間到達(dá),則 HTTP 請(qǐng)求返回?cái)?shù)據(jù)并關(guān)閉;否則,該請(qǐng)求只是失效。不管是哪種情況,一旦一個(gè)請(qǐng)求關(guān)閉,另一個(gè)請(qǐng)求將重新建立。盡管結(jié)果是對(duì)一個(gè) web 服務(wù)器的一系列重復(fù)連接,但它是一個(gè)比 Ajax 輪詢更有效的數(shù)量級(jí),特別是因?yàn)檫B接到的是一個(gè)專業(yè)服務(wù)器而不是直接連接到 web 應(yīng)用程序。
BOSH 上的 XMPP 允許 web 應(yīng)用程序通過(guò)一個(gè)原生連接持續(xù)與 XMPP 服務(wù)器通信??蛻舳送ㄟ^(guò)端口 80 上的 HTTP 上的一個(gè)標(biāo)準(zhǔn) URL 連接。然后,web 服務(wù)器將這個(gè)連接代理到由 XMPP 服務(wù)器操作的一個(gè)不同端口 — 通常是 7070 — 上的 HTTP URL。這樣,無(wú)論何時(shí)數(shù)據(jù)被發(fā)送到 XMPP 服務(wù)器,web 應(yīng)用程序只需使用一些資源,而 web 客戶端可以使用通常支持的 web 標(biāo)準(zhǔn)從防火墻后操作。維持 BOSH 的較長(zhǎng) HTTP 輪詢的開(kāi)銷主要由 XMPP 服務(wù)器而不是 web 服務(wù)器或 web 應(yīng)用程序承擔(dān)。web 服務(wù)器和 XMPP 服務(wù)器都不會(huì)受到與使用 JavaScript 進(jìn)行通信一樣的域限制,正是因?yàn)檫@一點(diǎn),消息才能夠被發(fā)送到其他 XMPP 服務(wù)器和客戶端。
現(xiàn)在,您理解了 XMPP 如何適合實(shí)時(shí) web,可以下載并設(shè)置它,以便開(kāi)始創(chuàng)建這個(gè) Pingstream 應(yīng)用程序。
獲取和安裝一個(gè) XMPP 服務(wù)器
在本小節(jié)中,您將安裝 Openfire XMPP 服務(wù)器并配置它來(lái)支持您的實(shí)時(shí) web 應(yīng)用程序。
選擇一個(gè) XMPP 服務(wù)器
有兩個(gè)領(lǐng)先的開(kāi)源 XMPP 服務(wù)器可以免費(fèi)下載。它們都應(yīng)用廣泛并通過(guò) GNU Public License version 2 許可,每個(gè)服務(wù)器都有自己的優(yōu)勢(shì)和缺點(diǎn):
l ejabberd:ejabberd 中的? e ?指的是 Erlang,一種軟實(shí)時(shí)編程語(yǔ)言。這一技術(shù)基石使 ejabberd 非常快。它還與 XMPP 核心和相關(guān)標(biāo)準(zhǔn)高度兼容。ejabberd 可以安裝在大多數(shù)環(huán)境中。
l Openfire:Openfire 用 Java? 語(yǔ)言編寫,用戶友好,安裝方便。
本教程使用 Openfire。
創(chuàng)建 Openfire 數(shù)據(jù)庫(kù)
為您的 Openfire 用戶和配置創(chuàng)建一個(gè)新的 MySQL 數(shù)據(jù)庫(kù)。通過(guò)使用 MySQL,您可以以編程方式從您的 PHP web 應(yīng)用程序添加、編輯、刪除和查詢您的 XMPP 服務(wù)器用戶,以及調(diào)節(jié)您的 XMPP 基礎(chǔ)設(shè)施以匹配您的 web 基礎(chǔ)設(shè)施。
如果您安裝了 phpMyAdmin,比如作為您的 XAMPP 安裝的一部分,那么您可以按照以下步驟創(chuàng)建數(shù)據(jù)庫(kù):
1. 從主界面選擇? Privileges 。
2. 選擇? Add a new user 。
3. 添加用戶細(xì)節(jié)(確保主機(jī)是 localhost;本教程假定您是在 localhost 上測(cè)試),選擇? Create database with same name and grant all privileges ,如? 圖 1 ?所示。不要向您的 Openfire 數(shù)據(jù)庫(kù)新用戶授予全局?jǐn)?shù)據(jù)庫(kù)特權(quán)。
圖 1. 在 phpMyAdmin 中添加一個(gè) Openfire 數(shù)據(jù)庫(kù)?
添加用戶和數(shù)據(jù)庫(kù)后,就可以安裝 Openfire 服務(wù)器了。
安裝 Openfire
下載 Openfire 安裝程序并運(yùn)行它,將 Openfire 安裝到您選擇的位置(參見(jiàn)? 參考資料 )。(您也可以選擇從 Openfire 的 Subversion 源代碼知識(shí)庫(kù)檢查 Openfire 的最新版本并本地構(gòu)建它,但這個(gè)主題超出了本教程的范圍。)收到提示時(shí),告知 Openfire 安裝程序在安裝完成時(shí)啟動(dòng)服務(wù)器。
服務(wù)器啟動(dòng)后,您應(yīng)該看到服務(wù)器狀態(tài)窗口,如? 圖 2 ?所示:
單擊? Launch Admin ?打開(kāi)一個(gè)基于 web 的向?qū)?,? 圖 3 ?所示,該向?qū)鸩脚渲媚?Openfire 服務(wù)器:
配置向?qū)г试S您選擇使用標(biāo)準(zhǔn)數(shù)據(jù)庫(kù)連接或嵌入式數(shù)據(jù)庫(kù)連接。選擇標(biāo)準(zhǔn)數(shù)據(jù)庫(kù)連接,以便您能夠使用您的 MySQL 數(shù)據(jù)庫(kù)。
從? Database Driver Presets ?列表選擇 MySQL。將您的服務(wù)器和數(shù)據(jù)庫(kù)名稱插入? Database URL ?字段。例如,對(duì)于在 localhost 上設(shè)置、名為 openfire 的 MySQL 數(shù)據(jù)庫(kù)而言,應(yīng)輸入:
jdbc:mysql://localhost:3306/openfire |
在向?qū)У南乱黄聊簧?,選擇將用戶帳戶存儲(chǔ)在數(shù)據(jù)庫(kù)中。輸入此前創(chuàng)建的數(shù)據(jù)庫(kù)用戶的用戶名和密碼,然后一直繼續(xù)到配置向?qū)ЫY(jié)束。此時(shí),您應(yīng)該已經(jīng)為您的 XMPP 服務(wù)器創(chuàng)建了一個(gè)服務(wù)器管理員并設(shè)置了域位置。
每用戶通知的插件
根據(jù)本教程的演示目的,您將只需使用在這里創(chuàng)建的兩個(gè)用戶。但是,如果您希望在您的應(yīng)用程序中支持復(fù)雜的每用戶通知,那么您需要能夠以編程方式從您的應(yīng)用程序的 PHP 部分添加和移除用戶。Openfire 的 User Service 插件通過(guò)一個(gè)用于 XMPP 用戶管理的 REST 界面向您提供這個(gè)功能。要安裝這個(gè)插件,從 Openfire 插件站點(diǎn)(參見(jiàn)? 參考資料 )下載它。插件本身是單個(gè)文件:userservice.jar。您必須將其放置到您的 Openfire 安裝的 /plugins 目錄中。
使用您建立的管理員憑證登錄到管理屏幕。單擊?
Edit Properties
(位于?
Server ports
?下方),記錄列示的服務(wù)器名稱。這個(gè)名稱將形成您的 JIDs 的域部分。這個(gè)名稱是不可互換的,比如,不能使用?
localhost
?替代?
127.0.0.1
,反之也不行。
單擊頂部導(dǎo)航菜單中的? Users/Groups ?并創(chuàng)建兩個(gè)新用戶。這些用戶將您在開(kāi)發(fā)過(guò)程中的測(cè)試用戶。
單擊? Server settings ,然后單擊? Offline messages 。由于您將 XMPP 用于界面通知,因此應(yīng)將? Offline Message Policy ?設(shè)置為? Drop ,如? 圖 4 ?所示。您不想保存用戶沒(méi)有登錄時(shí)收到的消息,否則,當(dāng)他們返回時(shí),可能會(huì)被數(shù)千條通知所 “淹沒(méi)”。
在? Server settings ?中,單擊? Server to Server 。對(duì)于本文,您不必連接到外部服務(wù)器,因?yàn)槟恍枰鳛楦蟮?XMPP 網(wǎng)絡(luò)的一個(gè)連接部分操作。因此,將? Service Enabled ?設(shè)置為? Disabled , Allowed to Connect ?設(shè)置為? White List 。這些設(shè)置將阻止未授權(quán)的連接造成破壞。
配置 Apache 以通過(guò) BOSH 轉(zhuǎn)發(fā) XMPP
Openfire 在 http://localhost:7070/http-bind 維護(hù)了一個(gè) HTTP 綁定 URL,以便通過(guò) BOSH 訪問(wèn)。要在端口 80 上使用這個(gè) URL,您必須配置 Apache HTTP Server 以將一個(gè) URL 轉(zhuǎn)發(fā)到這個(gè)位置。為此,您需要啟動(dòng)代理模塊。
打開(kāi)您的 http.conf Apache 配置文件并找到?
mod_proxy.so
和?
mod_proxy_http.so
?的?
LoadModule
?條目,它們默認(rèn)被注釋掉。移除前導(dǎo)的井字符(
#
),取消注釋。這個(gè)配置文件的 Dynamic Shared Object (DSO) Support 部分中的多個(gè)適當(dāng)?shù)男校ú灰欢ㄔ谝黄穑┈F(xiàn)在應(yīng)該類似于?
清單 8
:
清單 8.? 啟用 Apache HTTP Server? 中的代理支持
LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_module modules/mod_proxy.so |
在配置文件的末尾,添加? 清單 9 ?中的行(如果您沒(méi)有將 locahost 作為您的測(cè)試服務(wù)器環(huán)境,則應(yīng)將 127.0.0.1 替換為您的服務(wù)器 IP 地址):
清單 9. httpd.cof? 中的 XMPP? 代理規(guī)則
# 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 上使用了一個(gè)稍微不同的 URL:
/xmpp-httpbind
。這個(gè) URL 是 strophe.js(您稍后將用到的客戶端 JavaScript 框架)分配給一個(gè)用于設(shè)置 BOSH 端點(diǎn)的變量的值。
重啟服務(wù)器。現(xiàn)在,您可以開(kāi)始編寫使用 XMPP 的 web 應(yīng)用程序了。
創(chuàng)建使用 XMPPHP 的服務(wù)器應(yīng)用程序
在前面的小節(jié)中,您設(shè)置了服務(wù)器和插件。在本小節(jié)中,您將創(chuàng)建您的實(shí)時(shí)應(yīng)用程序的服務(wù)器端部分。
服務(wù)器端功能
您的應(yīng)用程序的 PHP 端將執(zhí)行以下兩個(gè)主要任務(wù):
1. 一個(gè) PHP 腳本將獲取一個(gè) RSS,并將在第一個(gè)條目自您上次檢查以來(lái)發(fā)生改變時(shí)通知您。
2. 一個(gè)前端腳本將初始化您的 JavaScript XMPP 客戶端。
首先在您的測(cè)試服務(wù)器的 web 根中創(chuàng)建一個(gè)名為 pingstream 的 PHP 新項(xiàng)目空間。如果您使用針對(duì) Eclipse 開(kāi)發(fā)環(huán)境的 PHP Development Tools 項(xiàng)目擴(kuò)展,那么要注意,要在 web 根、而不是 Eclipse 的默認(rèn)工作空間中創(chuàng)建這個(gè)項(xiàng)目(參見(jiàn)? 圖 5 ):
圖 5. 創(chuàng)建一個(gè)新的 Eclipse PHP 項(xiàng)目?
圖 5 ?顯示了這個(gè) Pingstream 項(xiàng)目的以下值:
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 是應(yīng)用最廣泛的針對(duì) PHP 的 XMPP 庫(kù)。與 ejabberd 和 Openfire 一樣,它也是免費(fèi)和開(kāi)源的,因此它是首次使用 XMPP 開(kāi)發(fā)人員的一個(gè)不錯(cuò)的起點(diǎn)。
下載 XMPPHP(參見(jiàn)? 參考資料 )。解包這個(gè)歸檔并將其插入您的新項(xiàng)目的 lib/xmpphp 子文件夾中。
最后,為節(jié)約您的 RSS 提要的解析時(shí)間,下載并安裝 Last RSS PHP 解析器(參見(jiàn)? 參考資料 )。
存儲(chǔ)服務(wù)器端設(shè)置
您將使用此前創(chuàng)建的兩個(gè)測(cè)試用戶之一作為您的通知發(fā)送方。在您的 /pingstream 的根中創(chuàng)建一個(gè)名為 config.inc.php 的文件,并添加? 清單 10 ?中的內(nèi)容:
<?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 文件還必須包含關(guān)于您的接收方的細(xì)節(jié),如? 清單 11 ?所示:
清單 11. 關(guān)于接收方的細(xì)節(jié)
// Set the receiving account details $CONFIG['receive'] = 'receivinguser@127.0.0.1'; |
您還需將少量數(shù)據(jù)緩存在一個(gè)文件中,因此 config.inc.php 需要包含一個(gè)可寫入的文件路徑,如? 清單 12 ?所示:
清單 12.? 設(shè)置存儲(chǔ)臨時(shí)文件的位置
// Full path to your cache directory, with trailing slash $CONFIG['cachedir'] = '/tmp/'; |
如果您位于一臺(tái) Microsoft? Windows? 機(jī)器上,那么您可以將這個(gè)位置設(shè)置為一個(gè)空字符串。否則,確保您指定的目錄包含一個(gè)完整路徑和一個(gè)結(jié)束斜杠,并被設(shè)置為全局可寫入(world-writeable)。
定義數(shù)據(jù)源
您的 Pingstream 應(yīng)用程序?qū)z查 IBM developerWorks Web development 專區(qū)提要獲取更新,因此,將? 清單 13 ?中的內(nèi)容添加到 config.inc.php:
清單 13. 檢查一個(gè) developerWorks 提要獲取更新
// Set the RSS feed you're going to check $CONFIG['rss'] = 'http://www.ibm.com/developerworks/views/web/rss/libraryview.jsp'; |
現(xiàn)在,創(chuàng)建另一個(gè)新文件:/lib.inc.php。這將是您的庫(kù)文件,包含在您的應(yīng)用程序的主控制器頁(yè)面和用戶界面頁(yè)面中。
lib.inc.php 的頂端必須引用 config.inc.php(主 XMPPHP 庫(kù))和 lastRSS,如? 清單 14 ?所示:
<?php // Load libraries require_once('XMPPHP/XMPP.php'); require_once('config.inc.php'); require_once('lastRSS.php'); |
接下來(lái),您將創(chuàng)建一個(gè)函數(shù)來(lái)通過(guò) XMPP 將一條消息發(fā)送到客戶端。通過(guò) XMPPHP 完成這個(gè)任務(wù)很簡(jiǎn)單,只需使用在您的配置文件中保存的憑證建立一個(gè)連接,發(fā)送消息,然后關(guān)閉連接。
在這個(gè)函數(shù)的第一部分中,您創(chuàng)建了一個(gè)新的 XMPPHP_XMPP 對(duì)象,如? 清單 15 ?所示:
清單 15. 創(chuàng)建一個(gè)新的 XMPP 連接對(duì)象
// Load configuration global $CONFIG; $conn = new XMPPHP_XMPP( $CONFIG['connect']->host, 5222, $CONFIG['connect']->user, $CONFIG['connect']->pass, $CONFIG['connect']->resc); |
注意,XMPPHP 通過(guò) XMPP 通信的默認(rèn)端口 5222 連接到您的 XMPP 服務(wù)器。盡管您的客戶端通信需要使用 BOSH,但服務(wù)器端沒(méi)有這個(gè)要求。
要連接到 XMPP 服務(wù)器,需要發(fā)送一個(gè)初始連接請(qǐng)求,一直等到接收到您的 XMPP 會(huì)話已經(jīng)啟動(dòng)的通知,然后發(fā)送一個(gè)聯(lián)機(jī)狀態(tài)節(jié)來(lái)聲明您處于在線狀態(tài)(參見(jiàn)? 清單 16 ):
$conn->connect(); $conn->processUntil('session_start'); $conn->presence(); |
下一步是發(fā)送消息本身,該消息已被預(yù)先填充到一個(gè)名為 $message 的變量中(參見(jiàn)? 清單 17 ):
$conn->message($CONFIG['receive'], $message); |
然后您使用 $conn->disconnect(); 斷開(kāi)連接。
在此過(guò)程中,您可能會(huì)遇到錯(cuò)誤。 清單 18 ?將清單? 15 、 16 ?和? 17 ?中的代碼放到一個(gè)將插入到 lib.inc.php 中的函數(shù)中。在這個(gè)過(guò)程中,它將發(fā)送一條消息的業(yè)務(wù)封裝到一個(gè) try/catch 語(yǔ)句中,該語(yǔ)句將把任何異常消息寫入錯(cuò)誤日志中。
清單 18. 完整的 send_notification 函數(shù),包含 try/catch 語(yǔ)句以記錄錯(cuò)誤
/** * 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()); } } |
您可以對(duì)任意數(shù)量的公共通知應(yīng)用程序使用這個(gè)簡(jiǎn)單的機(jī)制。這里,您向訪問(wèn)站點(diǎn)的所有用戶發(fā)送相同的通知,但通過(guò)以下機(jī)制來(lái)定制通知是件麻煩事:
每個(gè)用戶被賦予一個(gè)惟一會(huì)話字符串,或者一個(gè)特定于某個(gè)特殊興趣或搜索的共享字符串;
這個(gè)字符串作為資源片段被添加到接收方的 JID;
消息然后被發(fā)送到 user@domain/session-string JID。
獲取外部數(shù)據(jù)
接下來(lái),您需要一個(gè)函數(shù)(參見(jiàn)? 清單 19 )來(lái)從您的指定提要檢索最新的 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; } } |
這個(gè)函數(shù)初始化 lastRSS,加載您配置的 RSS 提要(這里是 IBM developerWorks Web development 專區(qū)的最新文章提要),將最頂端的條目返回為一個(gè)數(shù)組。擁有這個(gè)最新提要條目后,您需要知道自從上次檢查以來(lái),該條目是否被更改。為此,您需要使用一個(gè)小型文本文件。為保護(hù)應(yīng)用程序,最好使用數(shù)據(jù)庫(kù)或另一種方法,但對(duì)于測(cè)試目的,可以放心使用一個(gè)小型文件緩存。這個(gè)緩存在您每次檢查時(shí)都存儲(chǔ)最新提要條目的 URL;如果這個(gè) URL 更改,那么您就有一個(gè)新條目,應(yīng)該通知用戶。另一個(gè)函數(shù) feed_has_changed 將據(jù)此返回 true 或 false。不管是哪種情況,它都會(huì)將這個(gè)最新 URL 保存到緩存文件中,為下次檢查做好準(zhǔn)備。 清單 20 ?展示了 feed_has_changed 函數(shù):
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; } |
您將把最新條目的一個(gè)簡(jiǎn)單的 HTML 編碼版本傳遞給客戶端。在更高級(jí)的應(yīng)用程序中,可以以 JavaScript Object Notation (JSON) 或 XML 編碼該條目,多包括一些元數(shù)據(jù),并允許客戶端 JavaScript 根據(jù)設(shè)備和瀏覽器適當(dāng)格式化它。但是,對(duì)于現(xiàn)在, 清單 21 ?中的版本就夠用了。
清單 21. 將一個(gè)提要條目封裝在簡(jiǎn)單的 HTML 中
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 目錄中創(chuàng)建一個(gè)名為 backend.php 的新 PHP 文件。使用? 清單 22 ?中的簡(jiǎn)單 PHP 腳本作為文件內(nèi)容。這個(gè)腳本負(fù)責(zé)檢索 RSS 提要并通過(guò) XMPP 將最新提要條目的 JSON 編碼版本發(fā)送到您的接收方。
清單 22. 通過(guò) XMPP 發(fā)送封裝的提要條目
<?php require_once('lib.inc.php'); if ($lastitem = get_last_feed_item()) { if (feed_has_changed($lastitem['link'])) { send_notification(last_item_html($lastitem)); } } |
這就是將動(dòng)態(tài)通知發(fā)送給應(yīng)用程序的公共用戶所需的全部?jī)?nèi)容。理想情況下,您應(yīng)該將 backend.php 腳本作為一個(gè)定期時(shí)間任務(wù)運(yùn)行。但是,對(duì)于測(cè)試目的,您可以通過(guò)一個(gè) web 瀏覽器手動(dòng)執(zhí)行該腳本。
瀏覽器應(yīng)用程序:Strophe.js 和 jQuery
在本小節(jié)中,您將編寫一些 JavaScript 函數(shù),以便通過(guò) BOSH 上的 XMPP 接收消息,并構(gòu)建一個(gè) HTML 用戶界面來(lái)顯示接收到的通知。
創(chuàng)建用戶界面
現(xiàn)在您需要?jiǎng)?chuàng)建用戶界面來(lái)接收通知。Strophe.js 是用于通過(guò) BOSH 發(fā)送和接收 XMPP 數(shù)據(jù)的常用 JavaScript 庫(kù)。對(duì)于 Pingstream 中的目的,您只需接收數(shù)據(jù),盡管有一點(diǎn)是顯而易見(jiàn)的:雙向通信允許您快速構(gòu)建豐富的協(xié)作環(huán)境。
盡管有幾個(gè)版本,但 Strophe 的 JavaScript 版本作為一個(gè)基于瀏覽器的 XMPP 客戶端對(duì)您而言是最有用的。下載壓縮包(參見(jiàn)? 參考資料 )并將其解壓縮到 pingstream 的 strophejs 文件夾中。
jQuery JavaScript 框架極大地簡(jiǎn)化了事件處理和 DOM 操作。本文提供的 Strophe.js 示例廣泛使用該框架,這兩者簡(jiǎn)直是 “天生一對(duì)”。下載 jQuery(參見(jiàn)? 參考資料 ?中的鏈接)并將這個(gè)縮微版放到 pingstream 中的 jquery 文件夾中。
新建一個(gè) index.html 文件。在該文件中包含對(duì)剛才下載的 Strophe 和 jQuery 庫(kù)的引用,以及對(duì)稍后即將定義的 pingstream.js 庫(kù)的引用。在?
body
?元素中,添加一個(gè) 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> |
創(chuàng)建 JavaScript 文件 — pingstream.js — 您剛才在? 清單 23 ?中引用的。在 pingstream.js 的頂端,定義此前在 Apache 中配置的 BOSH 代理端點(diǎn),如? 清單 24 ?所示:
清單 24.? 設(shè)置 BOSH? 端點(diǎn)
var BOSH_SERVICE = '/xmpp-httpbind'; var connection = null; |
當(dāng)頁(yè)面完全加載后,您想自動(dòng)連接到 XMPP 服務(wù)器。您可以使用 jQuery 的?
$(document).ready
?調(diào)用實(shí)現(xiàn)這個(gè)目標(biāo);其中,您新建一個(gè)?
strophe.js Strophe.Connection
?對(duì)象并用它連接到服務(wù)器,如?
清單 25
?所示:
清單 25.? 建立一個(gè)通過(guò) BOSH? 的連接
$(document).ready(function () { connection = new Strophe.Connection(BOSH_SERVICE); connection.connect( "sendinguser@127.0.0.1", "sendingpass", onConnect); }); |
更健壯的選項(xiàng)
對(duì)于本教程的目的,您正在使用此前定義的發(fā)送方。對(duì)于一個(gè)更健壯的應(yīng)用程序,更好的方法可能是為每個(gè)注冊(cè)應(yīng)用程序用戶創(chuàng)建一個(gè)新用戶,并將每個(gè)用戶訂閱到一個(gè) “發(fā)布-訂閱” 界面。或者,如果您將用戶名和密碼留空并將 XMPP 服務(wù)器配置為接受這種類型的連接,那么 Strophe.js 可以匿名登錄。在這些情況下,將針對(duì)每個(gè)匿名用戶動(dòng)態(tài)創(chuàng)建一個(gè) JID;這些 JID 必須受到管理。最后,您還可以擴(kuò)展 XMPP 聊天室。
在?
清單 25
?中,
Strophe.Connection.connect
?方法包含一個(gè)對(duì)?
onConnect
?函數(shù)的引用,作為它的一個(gè)參數(shù)。
onConnect
?將在連接建立后立即啟動(dòng)。您可以利用這個(gè)機(jī)會(huì)來(lái)為入向消息添加一個(gè)通知處理程序;您在這里注冊(cè)了一個(gè)名為?
notifyUser
?函數(shù)。隨后,您發(fā)送了一個(gè)簡(jiǎn)單的聯(lián)機(jī)狀態(tài)節(jié)。
要確保您可以連接并接收新消息,您還需向用戶發(fā)送一個(gè)友好通知。
將?
清單 26
?中的代碼添加到您的 JavaScript 文件中的?
$(document)ready
?調(diào)用上方:
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()); } |
最后,由于您注冊(cè)了通知處理程序,因此,只要 XMPP 客戶端接收到消息節(jié),Strophe.js 就會(huì)調(diào)用?
notifyUser(msg)
?函數(shù)。
msg
?參數(shù)是 XML 節(jié)本身的一個(gè)表示,可以如?
清單 27
?所示查詢:
var elems = msg.getElementsByTagName('body'); var body = elems[0]; $('#notifications').append(Strophe.getText(body)); |
理想情況下,您希望對(duì)消息進(jìn)行限制,以便只顯示您的服務(wù)器端發(fā)送用戶發(fā)送的消息。您可以將它封裝到構(gòu)成?
notifyUser
?函數(shù)主體的一個(gè)?
if
?語(yǔ)句中,如?
清單 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; } |
這個(gè)函數(shù)應(yīng)位于在?
清單 26
?中定義的?
onConnect
?函數(shù)上方。
最終效果
在一個(gè) web 瀏覽器中打開(kāi)您的 index.html 文件。您應(yīng)該會(huì)看到一個(gè)簡(jiǎn)單的標(biāo)題和一條消息,該消息稱更新將在下面顯示(這可能會(huì)使您回想起您發(fā)給自己的測(cè)試通知,稱 XMPP 連接正在成功運(yùn)行)。
現(xiàn)在加載 backend.php。就像變戲法一樣,來(lái)自 IBM developerWorks Web development 專區(qū)的最新更新將顯示在您的頁(yè)面上。其他帶有 RSS 提要的示例源包括 Twitter 帳戶、通訊社、以及來(lái)自服務(wù)器監(jiān)控軟甲的更新提要。
這是開(kāi)發(fā)一個(gè)強(qiáng)大平臺(tái)的簡(jiǎn)單起點(diǎn)。Strophe.js 能夠促進(jìn)應(yīng)用程序的雙向通信,盡管更簡(jiǎn)單的方法是使用標(biāo)準(zhǔn)的 jQuery HTTP 回?fù)軄?lái)將用戶輸入送入系統(tǒng),從而避免為您的應(yīng)用程序編寫一個(gè) XMPP 后臺(tái)監(jiān)控進(jìn)程的麻煩。更令人興奮的是,當(dāng)您 web 服務(wù)器用作 BOSH 代理時(shí),完全無(wú)需太多來(lái)自服務(wù)器端 web 應(yīng)用程序的輸入,兩個(gè)或更多 web 客戶端就能通過(guò) XMPP 相互通信。這種技術(shù)將對(duì)從辦公室協(xié)作軟件到游戲的很多軟件產(chǎn)生深遠(yuǎn)影響。
結(jié)束語(yǔ)
本教程討論了實(shí)時(shí) web 應(yīng)用程序的必要性,以及 XMPP 如何克服現(xiàn)有技術(shù)的缺點(diǎn)。為展示這種方法的效果,您使用 XMPPHP、Last RSS、Strophe.js、Openfire 和 PHP 開(kāi)發(fā)了一個(gè)簡(jiǎn)單的 RSS 更新通知應(yīng)用程序。
盡管需要一個(gè)附加服務(wù)器層和一些 JavaScript 新技術(shù),但 XMPP 比傳統(tǒng) Ajax 輪詢模型更加適合實(shí)時(shí) web 應(yīng)用程序。XMPP 更快,在開(kāi)發(fā)和系統(tǒng)基礎(chǔ)設(shè)置方面需要的開(kāi)銷更少,并且使用一個(gè)強(qiáng)大的新興 web 開(kāi)發(fā)標(biāo)準(zhǔn)。
下載
描述 |
名字 |
大小 |
下載方法 |
Pingstream 源代碼 |
pingstream.zip |
238KB |
參考資料
學(xué)習(xí)
l 您可以參閱本文在 developerWorks 全球網(wǎng)站上的? 英文原文 。
l? XMPP Standards Foundation :訪問(wèn) XMPP 官方站點(diǎn)。
l? 實(shí)現(xiàn)可擴(kuò)展消息傳遞和到場(chǎng)協(xié)議(XMPP) (M. Tim Jones,developerWorks,2009 年 9 月):探索 XMPP 的細(xì)節(jié),了解如何將它用于簡(jiǎn)單消息傳遞。
l? 使用 XMPP、SMS、pureXML 和 PHP 創(chuàng)建警報(bào)系統(tǒng) (Joe Lennon,developerWorks,2009 年 11 月):嘗試使用 XMPP 來(lái)將通知發(fā)送到 Google Talk。
l? Jabber (Gerhard Poul,developerWorks,2002 年 5 月):查看這個(gè)早期 XMPP 簡(jiǎn)介。
l? Ajax 和 XML: 將 Ajax 用于聊天 (Jack D Herrington,developerWorks,2007 年 12 月):了解如何使用 Ajax 輪詢實(shí)現(xiàn)一個(gè)實(shí)時(shí) web 應(yīng)用程序。
l? JavaScript Tutorial :了解如何使用這個(gè) web 腳本語(yǔ)言。
l? Ejabberd :進(jìn)一步了解這個(gè)用 Erlang 語(yǔ)言編寫的 XMPP 服務(wù)器。
l? My developerWorks: 個(gè)性化您的 developerWorks 體驗(yàn)。
l? IBM XML 認(rèn)證 :了解如何才能成為一名 IBM 認(rèn)證的 XML 和相關(guān)技術(shù)的開(kāi)發(fā)人員。
l? XML 技術(shù)庫(kù) :訪問(wèn) developerWorks XML 專區(qū),獲得廣泛的技術(shù)文章和技巧、教程、標(biāo)準(zhǔn)和 IBM 紅皮書。
l developerWorks? 技術(shù)活動(dòng) ?和? 網(wǎng)絡(luò)廣播 :隨時(shí)關(guān)注這些活動(dòng)中的技術(shù)。
l? developerWorks 播客 :收聽(tīng)面向軟件開(kāi)發(fā)人員的有趣訪談和討論。
獲得產(chǎn)品和技術(shù)
l? Openfire :下載 Openfire,這是一個(gè)基于 XMPP (Jabber) 協(xié)議的跨平臺(tái)實(shí)時(shí)協(xié)作服務(wù)器。
l? PHP :訪問(wèn)這個(gè) PHP 站點(diǎn),獲取這個(gè)應(yīng)用廣泛的腳本語(yǔ)言,該語(yǔ)言非常適合 Web 開(kāi)發(fā),可以嵌入到 HTML 中。本教程使用 PHP 5.2 或更高版本。
l? Apache HTTP Server :下載這個(gè) Apache web 服務(wù)器。
l? MySQL :下載這個(gè)開(kāi)源事務(wù)型數(shù)據(jù)庫(kù)。
l? DB2 Express-C :下載這個(gè)免費(fèi)版 IBM DB2 數(shù)據(jù)庫(kù)服務(wù)器,它是中小型企業(yè)應(yīng)用程序開(kāi)發(fā)的一個(gè)堅(jiān)實(shí)基礎(chǔ)。
l? Openfire 插件 :獲取用于 Openfire 的 User Service 插件。
l? XMPPHP :從這個(gè)項(xiàng)目的 Google Code 站點(diǎn)下載 PHP XMPP Library。
l? Last RSS :獲取 Vojtech Semecky 針對(duì) PHP 的 RSS 解析器(經(jīng)過(guò) GNU Public License version 2 許可)。
l? jQuery :下載 jQuery JavaScript 庫(kù),經(jīng)過(guò) MIT 或 GNU Public License 許可。
l? Strophe :下載 Strophe.js,這是一系列用于編寫 XMPP 客戶端的庫(kù)。它的許可允許對(duì)其進(jìn)行自由使用、修改和共享。
l? phpMyAdmin :獲取這個(gè)免費(fèi)軟件工具,它支持通過(guò) web 管理 MySQL。
l? IBM 產(chǎn)品評(píng)估試用版軟件 :下載或? 在線試用 IBM SOA Sandbox ,并開(kāi)始使用來(lái)自 DB2?、Lotus?、Rational?、Tivoli? 和 WebSphere? 的應(yīng)用程序開(kāi)發(fā)工具和中間件產(chǎn)品。
討論
l? XMPP Discussion :訂閱面向開(kāi)發(fā)人員、系統(tǒng)管理員和用戶的 XMPP 討論。
l? XML 專區(qū)討論論壇 :參與任何一個(gè) XML 相關(guān)討論。
l? developerWorks 博客 :閱讀這些博客并參與討論。
?
引用:
http://www.cnblogs.com/alex-blog/articles/2665665.html
?
?
補(bǔ)充:如果相關(guān)的環(huán)境都配置成功時(shí),使用xmpp.php庫(kù)進(jìn)行相關(guān)的消息發(fā)送如果出現(xiàn)如下的錯(cuò)誤,那么就是您的服務(wù)器上面的ssl沒(méi)有配置成功,具體的一個(gè)配置方法如下所示:
意思是不支持ssl,輸出phpinfo,發(fā)現(xiàn)自己的php環(huán)境沒(méi)有安裝ssl擴(kuò)展,有些默認(rèn)安裝了,有些沒(méi)有安裝
這里寫下windows下的安裝方法
1、拷貝PHP 目錄中的libeay32.dll, ssleay32.dll, php5ts.dll, php ext 目錄下 php_curl.dll 文件到
system32 目錄。(一定要拷貝全啊,不要丟掉哪個(gè),不然會(huì)報(bào)錯(cuò)的)
2、修改php.ini:配置好extension_dir ,去掉extension = php_curl.dll?
3、重起apache。
再次輸出phpinfo,看到如下內(nèi)容就安裝成功了
?這晨需要注意一下的是,您需要確認(rèn)你所更改的php.ini是否是真正的配置文件,我上回就碰到了一個(gè)問(wèn)題。在php的目錄下面,確實(shí)是存在一個(gè)Php.ini的文件,我改完相磁的配置以后,完全沒(méi)有反應(yīng),依然報(bào)如上的錯(cuò)誤。后面我使用phpinfo輸出一看才明白,其輸出的結(jié)果php.ini指向的文件并不是這個(gè)實(shí)際文件,而是另外的一名字,而后,我在那個(gè)文件中進(jìn)行了相應(yīng)的修改。結(jié)果可以正常的運(yùn)行。在此提醒一下大家。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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