來源:http://mcs.szu.edu.cn/user/shendasky/Article_16190
Jabber客戶端
現(xiàn)在網(wǎng)絡中最流行的程序,莫過于即時通訊軟件了,從ICQ到QQ,全世界約有7000萬人每天在使用它們。人們利用它來溝通、交流,它是繼電子郵件之后另一個最成功的通訊工具。如此成功的軟件模式引出了一系列出色產(chǎn)品的誕生:ICQ,Yahoo! Messenger, AOL Instant Messenger,MSN Instant Messenger及中國人用的最多的QQ,而其中有一個較之其他通訊程序更璀璨奪目的明珠,那就是Jabber工程。
Jabber是一個基于開放模式的軟件工程,現(xiàn)在的主要目的運用于即時通訊(Instant Messaging System),Jabber并非第一個發(fā)明者,但它擁有幾個非同一般的特點:
*基于XML
*分布式構架
*開放式協(xié)議與代碼庫
*方便的、可擴展的組件模式
這些特點使得jabber一出世,便深受矚目,可以毫不避諱的說,幾大流行通訊軟件(如Yahoo!,AOL Messager,還有tencent 的 QQ)都是從jabber的代碼庫中發(fā)展而來的。而它基于XML的通訊協(xié)議,使得跨平臺很容易就能實現(xiàn),現(xiàn)在的jabber已經(jīng)可以使PC,Palm(掌上電腦類)以及SMS(短信息)、WAP互相溝通無礙了。總之,jabber的發(fā)展激動人心,它極有可能成為未來的即時通訊標準。
以下介紹的是Jabber的工作狀態(tài),依據(jù)版本為最新的1.4。
Jabber前介
Jabber Session
整個 Jabber的交流是基于一個會話(session)過程的,Jabber會話開始后,就會同指定服務器的端口5222(或者是5223,如果使用SSL 進行加密的話)進行TCP連接。WellJabber作為一個演示性程序,沒有將SSL選項包含在內(nèi)——需要注意的是,并非所有的服務器端都支持SSL。
Jabber發(fā)送的數(shù)據(jù)流是由一個連續(xù)不斷的XML文檔構成的,它的根元素為<stream>,而只有當C/S兩端都注銷時——即發(fā)出 </stream>關閉標記,這個XML文檔才算徹底結束。在<stream./>里的子元素們都是做為命令出現(xiàn)的,各自包括自己的屬性、內(nèi)嵌元素,以此作為參數(shù)。
服務器/客戶端兩邊的連接都是異步傳輸模式,這不同于諸如POP這樣的協(xié)議,在你發(fā)出一個命令時,不需要等待另一端的回應,可以直接發(fā)出下一個(命令)。此外,服務器可以隨時向你發(fā)出指示(比如說,當你的一個好友上線或離線時),這就意味著你得隨時作好處理這些指示的準備。
那如何將一個回應同特定的命令對應起來呢?這是id屬性需要做的事。你發(fā)出一個命令時,需要包含這個屬性 ——以一個恰當?shù)奈ㄒ恢党霈F(xiàn),而從服務器返回的回應,也包括同樣的屬性值。具體處理時,可以用一個散列表(Hash Table)作為id值,以此來標識那些需要回應的請求等數(shù)據(jù)。當然,設置合適的id值是服務器端的事,客戶端所要做的是隨時接收server發(fā)來的指示,在編寫代碼時,可以開辟一條單獨的線程或利用一個select/event來響應接收的信息。這些在C/C++中都擁有良好的支持,但PHP則不行,因為它畢竟是腳本語言,不能進行系統(tǒng)函數(shù)的調用,所以WellJabber暫時只能做到請求/回應的模式,不能做到隨時處理主動接收的信息,也就是當好友發(fā)一個信息給你時,你沒有辦法去判斷、接收它。因為這個至少需要一個循環(huán)來處理接收,但在腳本中出現(xiàn)這個循環(huán)意味著你的程序在信息到來之前始終不能完成,這是相當可怕的。對 WellJabber來說,這的確是個不小的遺憾。
Parsing XML
實際處理時,最困難的部分可能就是解析XML文檔了,但幸運的是XML不同于HTML,它有著嚴格的語法定義和格式,比如所有標記和屬性都是大小寫敏感的,所有的屬性結束時都要求明確的關閉標記,屬性值、標記外的文本內(nèi)容都不得與XML保留字相同(如<、 >、&、’、” 這樣一些,如果需要可以用& entity;的形式代替),還有就是非ASCII碼字符集的文檔要求在<?xml>中明確標識,通常中文可以處理為:
<?xml version="1.0" encoding="GB2312" ?>
或<?xml version="1.0" encoding="BIG5" ?>
自己寫出一個XML解析程序是完全有可能的,但幸運的是,有很多標準的XML解析程序(庫)可供我們使用,比如使用PHP編寫的WellJabber 實際上就是利用expat作為解析模塊。這里要注意的一點是,你的解析程序必須能處理任何得到的XML數(shù)據(jù)片段,因為前面已經(jīng)說過,jabber中傳遞的 XML數(shù)據(jù)并非完整的,一個徹底結束的數(shù)據(jù)流(以<stream/>結尾)要到程序注銷時才能出現(xiàn)。
補充:Expat是一個很有名的XML解析程序,很多出色的軟件工程使用它做為XML文檔的解析模塊,譬如PHP及Perl等。
Writing XML
寫XML數(shù)據(jù)相對來說比較簡單了,但仍要注意必須寫得符合語法要求,少一個引號都可能引起服務器拒絕處理、甚至是斷開連接。
登錄/注銷Jabber會話
Opening the session
當你打開位于5222(或5333)端口上的socket后,需要發(fā)送一個標準的XML頭,以此來打開一個以<stream>開始的完整jabber會話。
<?xml version=”1.0” encoding =”UTF-8”?>
<stream:stream
to=”jabber.org”
xmlns=”jabber:client”
xmlns:stream=http://etherx.jabber.org/streams>
這樣就可以喚醒服務器了,這時服務器會回應大致如下信息:
<?xml version=”1.0” encoding=”UTF-8”?>
<stream:stream
from=”jabber.org”
id=”39ABA7D2”
xmlns=”jabber:client”
xmlns:stream=http://etherx.jabber.org/streams>
這時XML解析器就可以發(fā)揮作用了,它提取并保留當前的id值,因為本次Jabber Session就依靠它來標識了。
我使用TCP Echo Client測試了一下,的確是如[JPO]中所述的一樣,只是要手工輸入XML代碼比較麻煩。
Logging in
這時服務器等待你的身份驗證,你使用<iq>詢問[JPO 1.5]來發(fā)送你的認證信息,而服務器據(jù)此返回一個回應,指示你的登錄是否成功。[JPO 1.5.3.3]
具體來說,詢問使用jabber:iq:auth空間名稱和<username>以及<resource>元素來標識用戶的jabber ID和計算機的resource名[JPO 1.6.3] 。
在認證過程中,使用明文傳遞密碼是不提倡的,比較安全的做法是配合使用一個包含已編碼digest的<digest>元素。這個 digest是根據(jù)session ID和密碼組合成的字符串,結合SHA-1算法生成一個20個字節(jié)的散列,然后再將其轉變?yōu)椋矗皞€字節(jié)的16進制形式。當發(fā)出這個登錄詢問后,就開始等待回應了。接下來你會收到<iq type=”reply”>的數(shù)據(jù),至此,你已經(jīng)成功登錄了。假如回應中type="error",則表示登錄失敗,這時得立即關閉session了。
Logging Out
當要注銷退出時,只需簡單的發(fā)出”</stream:stream>”數(shù)據(jù)即可,這樣就通知服務器,本次XML數(shù)據(jù)流結束,然后關閉 socket。有時候,socket會出乎意料的關閉,比如說當Jabber服務器進程中斷時。比較嚴重的問題是,一般TCP Socket不能分辨清楚一個空閑連接和一個斷開的連接(可能發(fā)生在計算機崩潰或網(wǎng)絡斷開時),這時不會收到任何數(shù)據(jù),這對一個向jabber一樣的實時通訊協(xié)議來說,是個棘手的問題:你可以想象一下,此時你仍然在線,而你的好友列表還是有效,但除非你試圖發(fā)送一個訊息,否則你不會知道你已經(jīng)斷開連接了。更為嚴重的是,一些防火墻和路由器在發(fā)現(xiàn)你長時間沒有動作后,會做自動斷開連接。
有兩種方法來解決這個問題,一種比較簡單,就是每隔一分鐘左右的時間就發(fā)送一個bit的XML數(shù)據(jù),假如你的計算機沒有得到服務器的回應,操作系統(tǒng)就將檢查連接是否斷開,并指出一個錯誤。還有一種復雜的方法就是設置你操作系統(tǒng)的網(wǎng)絡API中套接字的”keep-alive”選項,只需將超時間隔調整到幾分鐘即可,這個方法是否可行,依賴于你的操作系統(tǒng)(在win32平臺上,可以用 setsockopt函數(shù)配合SO_KEEPALIVE或SO_KEEPALIVE_VALS來修改這個選項)。
- 用戶在線狀態(tài)
即時通訊客戶端程序中要處理的一個重要模塊就是,通知服務器用戶的在線狀態(tài)。當你登錄后或改變在線狀態(tài)時,都需要通知服務器。(比如QQ中經(jīng)常使用的“我在吃飯,請過一會兒再和我聯(lián)系”,“我正在工作中”等等,這些都屬于用戶的在線狀態(tài))
要報告一個狀態(tài)信息的改變,只需要發(fā)送一個<presence>元素[JPO 1.4]。它的類型屬性不是available就是 unavailable。你不需要添加"from"或"to"屬性,服務器在將你的狀態(tài)信息發(fā)給你的好友時,會自動添加它們。[JPO 1.4.1.1, 1.4.1.5]unavailable狀態(tài)可以很方便的使用戶處于“隱身”:在你的好友看起來,你就象根本沒有在線。
接受你好友的在線信息正好相反:你會收到他們發(fā)送的<presence>數(shù)據(jù)。當你登錄后,你會收到每位好友這樣的數(shù)據(jù),以更新你好友列表的內(nèi)容。這個元素可能包括一個使用jabber:x:delay命名空間的<x>標記,來通知你好友狀態(tài)信息最后改變的時間。[JPO 1.6.18, JPG p.89]只要你在線,這些狀態(tài)信息隨時都會發(fā)送給你。
- 管理好友列表
好友資料(Roster)的管理是一個比較頭疼的事情,至少從現(xiàn)在協(xié)議的描述來看。
How the roster works
好友資料的處理工作包括:用戶的狀態(tài),好友的狀態(tài)以及那些想加好友但尚未驗證通過的請求。Jabber服務器存儲用戶的好友資料,并負責在如下情況下通知已登錄的用戶其好友資料的改變:用戶添加或刪除一個好友,其他用戶在好友列表中添加或刪除你,用戶通過或拒絕加入好友的驗證。這些都籠統(tǒng)的稱作好友資料更新。這些更新通知都是作為<iq>元素(使用jabber:iq:roster命名空間)數(shù)據(jù)來發(fā)送的。當然,客戶端也可以主動請求好友資料的更新:這個在登錄后通常都應該進行一次,以更新本地客戶端的好友資料。
Subscribing & unsubscribing buddies
添加或刪除好友是通過<presence>元素來進行的,它的type屬性是subscribe或unsubscribe。接收或拒絕都是通過<presence>元素來進行的(請求和應答都有同樣的ID號)總之,當你的好友資料改變時,服務器就會主動通知你情況的改變。前面已經(jīng)說過,PHP編寫的WellJabber有很多限制,其中一條就是除非你主動要求更新好友資料,否則很難及時反映好友在線情況。
Manually updating the roster
如果你想更新服務器端的好友資料,可以發(fā)送<iq type=”set”>元素,你這樣做并不是添加或刪除好友,而是更新與好友相關的資料,比如他們的昵稱或所屬組名。[JPO 1.6.12]
More roster info
完整的好友信息可以在通過一個<iq>詢問接受vCard資料時獲得,前提是如果他們存儲了這樣的信息[JPO 1.6.26]。(關于vCard,實在又是一個很大的論題,所以作為演示例子的WellJabber沒有包含它)
用戶在線狀態(tài)
即時通訊客戶端程序中要處理的一個重要模塊就是,通知服務器用戶的在線狀態(tài)。當你登錄后或改變在線狀態(tài)時,都需要通知服務器。(比如QQ中經(jīng)常使用的“我在吃飯,請過一會兒再和我聯(lián)系”,“我正在工作中”等等,這些都屬于用戶的在線狀態(tài))
要報告一個狀態(tài)信息的改變,只需要發(fā)送一個<presence>元素[JPO 1.4]。它的類型屬性不是available就是 unavailable。你不需要添加"from"或"to"屬性,服務器在將你的狀態(tài)信息發(fā)給你的好友時,會自動添加它們。[JPO 1.4.1.1, 1.4.1.5]unavailable狀態(tài)可以很方便的使用戶處于“隱身”:在你的好友看起來,你就象根本沒有在線。
接受你好友的在線信息正好相反:你會收到他們發(fā)送的<presence>數(shù)據(jù)。當你登錄后,你會收到每位好友這樣的數(shù)據(jù),以更新你好友列表的內(nèi)容。這個元素可能包括一個使用jabber:x:delay命名空間的<x>標記,來通知你好友狀態(tài)信息最后改變的時間。[JPO 1.6.18, JPG p.89]只要你在線,這些狀態(tài)信息隨時都會發(fā)送給你。
管理好友列表
好友資料(Roster)的管理是一個比較頭疼的事情,至少從現(xiàn)在協(xié)議的描述來看。
How the roster works
好友資料的處理工作包括:用戶的狀態(tài),好友的狀態(tài)以及那些想加好友但尚未驗證通過的請求。Jabber服務器存儲用戶的好友資料,并負責在如下情況下通知已登錄的用戶其好友資料的改變:用戶添加或刪除一個好友,其他用戶在好友列表中添加或刪除你,用戶通過或拒絕加入好友的驗證。這些都籠統(tǒng)的稱作好友資料更新。這些更新通知都是作為<iq>元素(使用jabber:iq:roster命名空間)數(shù)據(jù)來發(fā)送的。當然,客戶端也可以主動請求好友資料的更新:這個在登錄后通常都應該進行一次,以更新本地客戶端的好友資料。
Subscribing & unsubscribing buddies
添加或刪除好友是通過<presence>元素來進行的,它的type屬性是subscribe或unsubscribe。接收或拒絕都是通過<presence>元素來進行的(請求和應答都有同樣的ID號)總之,當你的好友資料改變時,服務器就會主動通知你情況的改變。前面已經(jīng)說過,PHP編寫的WellJabber有很多限制,其中一條就是除非你主動要求更新好友資料,否則很難及時反映好友在線情況。
Manually updating the roster
如果你想更新服務器端的好友資料,可以發(fā)送<iq type=”set”>元素,你這樣做并不是添加或刪除好友,而是更新與好友相關的資料,比如他們的昵稱或所屬組名。[JPO 1.6.12]
More roster info
完整的好友信息可以在通過一個<iq>詢問接受vCard資料時獲得,前提是如果他們存儲了這樣的信息[JPO 1.6.26]。(關于vCard,實在又是一個很大的論題,所以作為演示例子的WellJabber沒有包含它)
發(fā)送信息時,使用一個<message>元素[JPO 1.3],它使用”to”屬性來標識接收者;反之,你接受包含”from”屬性的<message>元素,它標識了發(fā)送者。
實際上,任何人都可以發(fā)送信息給別人,你不需要特定的權限就可以查看到別人的在線狀態(tài)。這會造成信息的騷擾與泛濫嗎?要解決這個情況,就要使程序有對信息進行篩選的能力,只允許從好友處來的信息,其他一律過濾掉。
Message attributes
我們收到的任何信息都包括一個<form>屬性,它給出了信息的發(fā)送者。同電子郵件相比,它的認證更為可靠,因為這個屬性是由jabber服務器端來添加的,這就減少了發(fā)送者進行欺詐行為的可能性。
一個信息還應該包括一個<subject>元素,它標識了本次信息的主題,但顯示與否取決于接收者所使用的客戶端程序。
一個信息還可以包括一個時間戳,這是用一個<x>元素來實現(xiàn)的,它使用了jabber:x:delay命名空間。
而使用jabber:x:envelope命名空間還可以提供群發(fā)的功能,這就象傳統(tǒng)的電子郵件一樣。[JPO 1.6.20]
The message body
一個信息總是用<body>元素來包含其具體內(nèi)容的。[JPO 1.3.3.1]
當然也可以包含可選的元素<html>,它將提供HTML格式的信息。[JPO 1.3.3.3]但是需要注意的是,這個格式是基于XHTML的(w3.org制定的一種由HTML向XML過渡的格式)。
對于HTML的使用者來說,會發(fā)現(xiàn)XHTML與其有很大的不同,因為設計XHTML時就考慮了客戶端類型的限制(譬如說手機),具體體現(xiàn)為缺少一些常用的HTML元素,如<b>,<i>及<font>,但它們在XHTML中都有等價替代元素,如< strong>代替了舊的<b>,但一般指定色彩或格式時,都使用CSS(Cascading Style Sheet)。
Jabber支持加密的信息傳送,它使用包含jabber:x:encrypted命名空間的<x>元素來處理。[JPO 1.6.19]文檔中對這段描述并不是很清楚,因此WellJabber并沒有對加密提供支持。
Other types of content
與MIME不同,jabber信息并沒有一個標準的格式來容納圖片或聲音,這就意味著你無法在信息中包含一幅圖片的數(shù)據(jù),除非是使用超鏈接的形式來指示它。
你可以隨信息一起發(fā)送文件,但是文件的數(shù)據(jù)不能包含在<message>中,而是采用超鏈接的方式指明可以下載的文件。
Message types and threads
發(fā)送的信息可以使用”type”屬性來提示其顯示方式,如果沒有指明這個屬性,信息將獨立地顯示在單獨的窗口中。若”type=chat”則指明應使用 one-to-one(類似QQ的兩人世界)聊天界面來顯示。此外還有”type=groupchat”,詳細參見[JPO 1.3.1.1—— 1.3.1.4]。
最后有可能出現(xiàn)”type=error”這樣的屬性值,它表明在發(fā)送一個信息時出錯了(比較常見的是,發(fā)送信息給一個不存在的jabber地址)。這時的回應包含在一個<error>元素中。[JPO 1.3.1.3]
為幫助客戶端顯示信息在相應界面中,信息還可以包含一個<thread>元素,它包含一個指向信息流的唯一值,客戶端發(fā)送的第一個信息就應該包括一個唯一的線程ID,而后繼的信息都應該發(fā)送到此線程ID標識的同一個線程中。(JPO建議thread ID由發(fā)送者的jabber ID及當前時間以散列算法合成)
Message event
信息的發(fā)送者可以使用jabber:x:events命名空間來接受這樣的通告,即信息的接收者是否已經(jīng)查閱過本信息,或者他/她是否在進行回復。這是個全新的功能,在演示程序WellJabber中沒有體現(xiàn)。
Message expiration
信息的發(fā)送者可以使用jabber:x:expire命名空間來確定信息的發(fā)送時效。[JPO 1.6.22]如果信息是離線存儲的,當時效過去時,即使對方用戶登錄,該信息也不會發(fā)給他/她。
聊 天
Jabber的群組聊天或會議機制允許多人同時進行交流。
這種多人交流的方式在客戶端實現(xiàn)時是比較復雜的,這是大家所公認的,因為有兩套聊天協(xié)議在使用。群組聊天是最早采用的,而會議機制是新的,也更靈活(注意,現(xiàn)在只有jabber 1.4服務器版本才支持它——做為一個外接模塊)。實際上,協(xié)議本身仍在不段變化,還沒有最終形成標準。
Creating a chat room
在產(chǎn)生一個聊天室前,你需要有一個聊天室名和一個會議服務。服務可以由用戶來制定,或者通過發(fā)送jabber:iq:browse請求來檢索。聊天室名稱可以自己輸入,或者編程產(chǎn)生(比如,產(chǎn)生一個隨機的數(shù)字作為名稱)。
為確認聊天室名稱是否已被使用,可以發(fā)送<iq type=”get”>(含xmlns=”jabber:iq:browse”的命名空間)到聊天室,如果它不存在,你會收到error 404(沒有找到)錯誤,反之,如果其已存在,你就得重新為聊天室取個名稱。QQ中體現(xiàn)在自建聊天室這個版塊。
對于如何生成一個聊天室,有著不同的異議。編程者的實踐經(jīng)驗是先發(fā)送presence到聊天室,如果已存在就加入它,沒有則發(fā)送set請求來建立它(發(fā)送包含xmlns=”jabber:iq:browse”的<iq type=”set”>命令)
Joining a chat room
需要加入一個聊天室時(它的ID已經(jīng)由用戶指定或在接到聊天邀請時確定),首先發(fā)送一個<presence>元素。注意不要添加 resource名在發(fā)送中,這是老的groupchat的做法,現(xiàn)在的conference已經(jīng)不采用了。如果你需要向下兼容性,可以發(fā)送 resource name。
接下來,發(fā)送包含xmlns=”jabber:iq:browse”的<iq type=”set”>,這個請求包含了一個或多個<nick>元素,它指明了你希望加入的會議的別名。一旦你接到一個成功回應,也就意味著你已經(jīng)加入這個聊天室。
The chat’s roster
每個聊天室都有個人員列表,表明當前在聊天室中的人員。它會隨著人員加入或離開而改變。
通知客戶端聊天室人員的方法有很多種。首先,發(fā)給每個成員<presence>元素,在你加入這個聊天室或有其他成員改變在線狀態(tài)時(更新狀態(tài)、信息或是離開)。
此外,當成員列表改變時,自己會收到一個含有jabber:iq:conference命名空間的<iq>元素,它具體包括代理服務器上的 jabber ID以及當前成員的昵稱。描述conference本身的是包含多個屬性的<conference>元素,如果包含< user>子元素,則標識了當前的成員們,這時通常帶有”jid” 屬性和”name”屬性。
又如,當一個成員加入、離開或是改變其昵稱時,你就會收到一個類似的請求,它包含一個單獨的<user>元素。
最后,服務器會發(fā)送一個類似“某某加入了”或“某某離開了”樣式的消息。
隨便說一下,如果你希望在聊天室查找某人,可以使用包含jabber:iq:borowse命名空間的<iq>元素來發(fā)送他/她的proxy JID。
Chat invitations
聊天邀請使用包含<xmlns=”jabber:x:conference”>的請求來實現(xiàn)。
Sending and receiving messages
要發(fā)送一個信息到聊天室,可以發(fā)送”type”為”groupchat”的<message>元素到聊天室地址。發(fā)送一個私人信息,可以到他們的proxy ID。
收到的聊天信息依靠”groupchat”類型來辨識,可以從”from”地址去處掉resource ID,此時剩下來的就是聊天室ID了。
Jabber還支持IRC的“表情”聊天方式,這使聊天者能做出類似舞臺動作的行為。(這個在QQ中是很常見,也很有趣的)客戶端通過前綴”/me”來辨識這樣的信息,通常在顯示前應該插入使用對象的昵稱。
比如,可以發(fā)送這樣的信息”/me 笑瞇瞇的望著大家”,則客戶端就顯示為:
Huwell 笑瞇瞇的望著大家
Leaving the conference
要離開聊天室時,你只需要簡單的發(fā)送一個<presence type=”unavailable”>元素即可。
File Transfer
Jabber不直接支持文件的傳送,而是依靠“帶外數(shù)據(jù)”(out-of-band)即OOB機制通過URL來超鏈接文件。這種解決方案使得發(fā)送者的客戶端要么上傳文件到一個特定的FTP/HTTP/WebDAV服務器,要么打開另一個端口,運行一個常規(guī)服務在上面。這兩種方法下URL都會發(fā)送給接受者,他們便使用這個超鏈接來下載文件。注意,后一種機制在發(fā)送者隱藏在防火墻或NAT Server后時會失效,接收者不受影響。這種P2P的文件傳送功能非常有用。
OOB不是僅為文件轉送來設計的,它可用來傳送任何URL,比如一個到心愛站點的鏈接,盡管一個HTML消息也可以支持它。
有兩種相似的方法來傳送URL,一個就是將<x>嵌套在<message>元素中,并使用jabber:x:oob命名空間 [JPG p.92,JPO 1.6.23];第二種方法就是使用jabber:x:oob命名空間的<iq>元素請求[JPG p.53, JPO 1.6.9]后一種方法允許通過iq回應來確認。
[*}注冊新用戶
Jabber協(xié)議允許客戶端登記一個新的用戶,而不用通過web界面來或系統(tǒng)管理員來申請(當然,任何服務器都允許這樣做)。登記新的帳戶有多種方法。
登記時,首先連接到服務器并打開一個<stream>元素,這就好象是正常登錄。只是發(fā)送的是使用jabber:iq:register命名空間的<iq type=”get”>元素。[JPG p57-62]
如果服務器不允許登記新用戶的話,會回應一個錯誤。
下列資料可以做為編程者設計登記新用戶界面的參考:
<key>,這是一個需要隨著登記命令發(fā)送回服務器的認證字符串。
<instructions>,包含一個呈現(xiàn)給用戶的介紹。
<username><nick><password><name><first><last><email>
<addtress><city><state><zip><phone><url> <date><misc><text>,這些都是用戶的資料。屬于jabber Server1.4所需要的,如果開發(fā)出自己服務器版本,就可以自己定義這些用戶資料選項了。
當用戶一切就緒后,就可以發(fā)送包含上述資料的<iq type=”set”>回應了,然后等待服務器的響應。
如果注冊新用戶成功,你會收到一個包含空的請求的回應。你需要關閉連接,這個連接不能再做為登錄的連接重復使用了,你得另外打開一條新的。
如果注冊失敗了,你將得到錯誤的回應,如果錯誤代碼是409(沖突),這意味著你注冊的用戶名是無效的。
Updating registration
如果要更新注冊信息(如密碼或電子郵件)可以通過發(fā)送<iq type=”set”>元素來完成。
Canceling an account
如果你需要終止一個帳號,你先得通過發(fā)送一個<iq type=”get”>元素來獲得服務器的<key>,然后再發(fā)送包含<remove>子元素的<iq type=”set”>來達成。
[*}WellJabber功能模塊
開發(fā)一個jabber的客戶端不是一件簡單的事,事實上標準的客戶端應該包括:P2P聊天,聊天室,好友列表管理,資源管理,相關資料修改,信息加密處理,MSN網(wǎng)關(或者還包括Yahoo!,AOL等的信息轉換),在線搜索,認證控制,離線信息轉發(fā),文件互傳等。
一個比較成功的jabber客戶端是由jabber.com提供的JIM,此外還有很多優(yōu)秀的jabber客戶端,由于jabber的通訊協(xié)議是建立在 XML基礎上,而且是開放式的,所以任何語言都可以用來編寫客戶端,最常見的是Delphi,Java,C/C++,Perl,此外還有PHP, Python,JavaScript,甚至是Flash ActionScript都可以拿來編寫這樣的客戶端,我覺得一個開放式的應用模式才是成功的,才會有長足的進步,這就象電子郵件,我們很難想象如果電子郵件的格式被一家所壟斷,只能使用一家所編寫的郵件程序去接收,那它還會這么普及、全球通用嗎?在這方面國內(nèi)的騰訊公司做的就很不好,顯然易見,QQ的代碼也是從jabber中剝離出來的,但是QQ卻不肯公開它的通訊協(xié)議,一心只想做國內(nèi)即時通信的老大,這會造成兩個后果:一個是處在競爭壓力小的情況下,它會停止不前,沒有進步。
我們看到,現(xiàn)在的QQ客戶端同以前的沒有什么太大的區(qū)別,不過界面更花哨些而已,沒有利益的驅動,騰訊現(xiàn)在連Linux,Palm等平臺都沒有推出,實際上很可悲。另一個是它只能在國內(nèi)發(fā)展,走不出國門,一旦即時通訊的國際標準制定,那它再這么固步自封,就會成為不合格的產(chǎn)品,下場也好不到哪去。所以希望騰訊公司能趕快覺醒,不要壟斷這個現(xiàn)在看起來很壯大的行業(yè)。QQ雖然很火,但比起Flash,Netscape又如何?這些世界聞名的軟件都是公開格式,甚至是代碼的,騰訊還不該向這些軟件學習嗎?當年的Netsacape幾乎是獨步天下,可還是被IE后來居上了,現(xiàn)在微軟又在XP中捆綁了MSN,騰訊再不當心,可真要尷尬了。
WellJabber是使用PHP寫的,是典型的B/S結構程序,之所以這樣考慮,是想使WellJabber具有跨平臺的特性,能夠在win32, Linux,Unix,Mac等系統(tǒng)上都順暢的運行,因為它是利用瀏覽器做為承載平臺的。而且PHP發(fā)展到今天(最新的版本是Version 4)已經(jīng)很強大了,它具有多種函數(shù)庫,如同C語言一樣,甚至融合的比C更體貼,就Jabber來看,PHP擁有必不可少的XML解析函數(shù),還有網(wǎng)絡連接函數(shù),以及加密函數(shù)(Hash散列,Base_64等),總之使用PHP來寫jabber的演示程序的確很方便,但PHP也不是十全十美的,畢竟,通過HTTP端口,很多有用的功能都實現(xiàn)不了,而且調用的不是系統(tǒng)級的函數(shù)(如connect),效率有所下降。
本程序只預計包含五個功能模塊:用戶注冊,用戶登錄,獲取好友列表,發(fā)送信息,用戶注銷。
考慮到面向對象的特性,WellJabber程序采用了類的定義,以方便腳本調用,支持類是PHP中很有用的特點,特別是數(shù)據(jù)庫操作,如果能把一般SQL行為用類來封裝,那么在修改數(shù)據(jù)庫類型時將會很方便,做到以做少的改動支持最大的兼容性。
WellJabber中的類定義放在jabber.inc中,一共有6個行為,分別是:
jabber->connect (server[, port])
jabber->Login (username, password, resource,server[, port])
jabber->messages (recipient, subject, body, type)
jabber->register(username, password, email, resource, server[, port])
jabber->GetRoster()
jabber->_display_error_message()
在定義好這幾個類以后,腳本就可以很方便的實現(xiàn)jabber的通訊功能,而不必重復代碼。配合模版的設計,使得PHP版的Jabber更為方便靈活。
根據(jù)jabber文檔中的描述,WellJabber類中定義了以下的成員變量,其值與特定含義分別是:
name——用戶的真實姓名;
email——用戶的電子郵件;
password——用戶選用的密碼;
username——用戶登錄名稱;
resource——用戶的Location辨識名;
sid——本次session的唯一標識
server——登錄服務器名稱;
port——登錄服務器端口名稱;
error_code——發(fā)送錯誤的代碼;
error——發(fā)送的錯誤(描述)
connect——本次連接的文件指針
roster——好友資料數(shù)組
首先,看一看內(nèi)部的出錯處理函數(shù):_display_error_message():
這里error的錯誤描述實際上就是[JPO]的附錄所指明的錯誤描述代碼,這是標準的,由服務器發(fā)回的。
Jabber類中要處理的錯誤列表為:
Bad Request
這個表明jabber客戶端發(fā)送的數(shù)據(jù)不能為server端理解,通
常是由于數(shù)據(jù)流不符合jabber協(xié)議而引起的。(譬如,jabber客戶端發(fā)送了一個subscriptioj給自己,或者是發(fā)送了一個不含to屬性的數(shù)據(jù)流。
Unauthorized
這個表明客戶端的身份請求驗證失敗,當客戶端發(fā)出錯誤的密
碼或者是不存在的用戶名時會發(fā)生這種情況。
Service Unavailable
這個錯誤主要發(fā)生在服務器無法處理客戶端的請求時,譬如,
當我們要發(fā)送一個消息給離線好友,但接收者的服務器不支持離線信息存儲的機制,就會返回這個錯誤。
Remote Server Timeout
當試圖連接一個服務器而超時時就會發(fā)生這個錯誤。比如說一
個不正確的服務器名稱被指定時。
Payment Required
這個錯誤是為未來使用制定的,現(xiàn)在不會發(fā)生。
Forbidden
這個錯誤發(fā)生時表明,服務器理解客戶端的請求,但拒絕處理
它。現(xiàn)在主要發(fā)生在當注冊時密碼存儲錯誤時。
Not Found
這個主要是在服務器無法找到與這個客戶端送來的數(shù)據(jù)包匹
配的JabberID時發(fā)生的。
Not Allowed
本錯誤主要是當服務器根據(jù)該數(shù)據(jù)包中的JabberID判定本次
Jabber行為無效時產(chǎn)生的,譬如當非管理員用戶向服務器發(fā)送一個管理員數(shù)據(jù)命令時。
Registration Required
這個錯誤現(xiàn)在尚未開始使用。
Internal Server Error
當服務器發(fā)生了未知錯誤時,就會返回這個error,要防止這
種情況發(fā)生主要是從客戶端入手,要保證發(fā)送的數(shù)據(jù)包的正確性。
Invalid Parameter
無效的參數(shù)錯誤。
在發(fā)生上述錯誤時,_display_error_message()都會做出正確的處理,實在有未知的錯誤發(fā)生時,也會提示與管理員聯(lián)系。
下面一一分析成員函數(shù),先看用戶登錄時所用的:
function connect ($server, $port = "5222")
注意這里默認的端口為5222。如果你使用了SSL登錄也可以改為5223。函數(shù)里首先是驗證傳遞過來的參數(shù)的合法性。也就是server,username,password及resource不能為空,否則就報告錯誤。
接著是與server端的5222端口相連接,這里使用了fsockopen函數(shù),這個函數(shù)功能很強大,它與服務器做了一個TCP連接,并且它返回了一個文件指針,可用于其他的文件函數(shù)(如fgets、fgetss、fputs、fclose或feof等)。可以說沒有它,jabber的功能就實現(xiàn)不了,因為jabber主要是依靠與server的連接,交互數(shù)據(jù)流來實現(xiàn)的,用其他語言如C/C++可以很方便的調用connect函數(shù)(以及之后的 send、receive函數(shù)),同樣PHP有fsockopen()也很不錯。
登錄時使用Login ($username, $ password, $resource, $server, $port = "5222"),當jabber->connect()打開連接后,開始向server端發(fā)送數(shù)據(jù),譬如登錄時發(fā)送的XML數(shù)據(jù)包,隨后要讀入返回的流,這時會產(chǎn)生一個錯誤,因為使用fread或fgets等PHP文件操作函數(shù),都要求讀入一定的量字符數(shù)(按照參數(shù)),或者是讀到行尾或文件尾,但是由服務器返回的數(shù)據(jù)是一個完整XML流的一部分,沒有所謂的行分隔,我們預先也無法知道此次返回多少字節(jié),如果寫成fgets($fp, 1024)這樣的,就會使該腳本陷入延時,因為fgets行為就是想讀入1024個字節(jié)或者是到行尾/文件尾,等如果本次數(shù)據(jù)量小于1024字節(jié),就會陷入這個函數(shù),不能正確返回值。
在查閱了php.net的最新函數(shù)后發(fā)現(xiàn),我們可以依靠socket_get_status()函數(shù)的unread_bytes特性來間接處理,說實話,這個方法有點勉強,但由于PHP語言的限制,實在沒有其他方法來很好的處理它,如果是C/C++,那就很方便了,recv()函數(shù)自己知道收回多少數(shù)據(jù),再不然配合Peek參數(shù)也可以預知本次數(shù)據(jù)量。
而使用socket_get_status()方法,就要分兩步做,首先使用fgets()類型的函數(shù)讀取一次數(shù)據(jù)(可以讀一個字節(jié)),然后再用 unread_bytes得知本次未讀數(shù)據(jù),依據(jù)這個準確的字節(jié)數(shù),再調用fgets()一次就可以全部讀取了。由于要分兩步做,所以效率不是很高的。然后拼接兩次得到的字符串,就有了本次回應的數(shù)據(jù)流了。
收到XML數(shù)據(jù)后就要來解析它,PHP有很強大的XML解析函數(shù),因為它是依靠expat做后臺模塊的。首先要創(chuàng)建一個XML解析器,就好象與MySql數(shù)據(jù)庫做連接一樣,都是準備工作:
xml_parser_create();//使用缺省編碼ISO=8859-1
在下面的函數(shù)中都要用到這個解析器,然后調用xml_set_element_handler()來設置起始及結束元素的處理,第一個參數(shù)就是上面說到的解析器,第二個和第三個是XML特有規(guī)定的函數(shù)處理格式的名稱,主要是:
StartElementHandler(int parser, string name, string attr)
第一個參數(shù)也是解析器,第二個用于保存XML元素名稱,缺省情況下,它們會以大寫形式出現(xiàn)。第三個是數(shù)組,用以保存當前元素的屬性及對應值。有了它,可以利用PHP特有的each逐個讀出來。
我們在StartElementHandler中將本次要用到的元素屬性賦值,以便下面的調用判斷,如登錄中就是要對$jabber_type值是否為result進行判斷,如果是表明登錄成功,如果不是那就是登錄失敗了。
接下來是GetRoster()行為,使用它可以獲得當前用戶的好友列表,我們發(fā)送:
<iq type="get"><query xmlns="jabber:iq:roster"/></iq>
給服務器,即索要當前會話用戶的好友列表,然后服務器會返回一系列數(shù)據(jù)流,里面包括了好友的名稱,JID(jabber唯一標識,就好象是QQ中的數(shù)字號碼)以及認證狀態(tài),如果還沒有通過好友的認證,那subscription屬性就會為none,WellJabber中采用了$jabber-> roster成員變量來接收這一系列的值。需要注意的是每次成員函數(shù)調用時都使用同一個連接,i.e.$jabber->connect,所以單個行為不要調用fclose來關閉它,可以在類的析構函數(shù)中調用。
SenMessage()發(fā)送消息給好友,這里比較簡單,當獲取好友的JID時,發(fā)送相應的數(shù)據(jù)流即可,這里要注意的是,發(fā)送人不需要自己填寫,在經(jīng)過服務器處理后,會由服務器來添加“from”屬性,這個是為了防止發(fā)送垃圾信息,前面已經(jīng)說過了。
最后是登記新的用戶帳號,這里分四步:
首先,要向服務器發(fā)送一個連接請求,就如同登錄時所發(fā)送的一樣;
接著,客戶端會收到回應的數(shù)據(jù)流,這里包含了重要的id,是標識本次會話的唯一值;這時,我們要發(fā)送本次想注冊的用戶名,resource名及密碼,注意這里的<iq>請求要包含上面得到的id,而且密碼應該采用加密的形式,但WellJabber只是一個演示程序,所以采用了明文發(fā)送的形式;
最后,服務器返回<iq di=’sesseion id’type=’result’>
代表本次登記注冊成功。這樣就完成了一個新用戶的注冊。
然后就可以使用該帳號進行登錄了。注意,這里要重新與服務器打開一個連接,原先的連接已經(jīng)不能用來登錄了。
PHP版的WellJabber所擁有的功能已經(jīng)描述完了。當然,從它來看Jabber工程只能是管中窺豹,Jabber中許多有用的思想和特點它都沒有體現(xiàn),譬如說實時接收、文件交換、郵件轉發(fā)、聊天室系統(tǒng)甚至是跨平臺交流(如mobile)。但由于Jabber開放和易用的特性,我們看到,任何人都可以用自己喜歡的語言去處理jabber、去理解jabber,這么博大包容的特性也許就是它最吸引人的地方,Jabber的前途將無可限量。