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