1. 程式人生 > >Jabber 客戶端 解析

Jabber 客戶端 解析

考慮到面向物件的特性,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的前途將無可限量。