gloox會議和服務發現功能

通過gloox實現會議功能,在XMPP協議中被描述爲多人聊天的模式,當然這個不是簡單的多人聊天,你可以創建聊天室,並且設置密碼,邀請某人進聊天室,將某人踢出聊天室 等,從某種角度來說,會議是多人聊天的一種特殊情況。在這裏我主要說一下關於會議的創建和使用(後面我統一將多人聊天稱爲會議)。

examples目錄中有個muc_example.cpp的文件,裏面是一個創建多個聊天(會議)的示例,但它是一個非常簡單的示例,我們需要更深入地去了解。

要創建會議,需要了解三個類(接口),分別是MUCRoom類,MUCRoomHandler處理器(接口),MUCInvitationHandler處理器(接口)。下面分別對這三個類描述一下:

MUCRoom類,這個類很重要,那麼它是什麼意思呢?如果只從examples中的示例代碼中,根本看不出其用處來,或者只能是理解一半,即使你new一個這樣的對象出來,其也並不是指創建了會議,而這個類僅僅只是說明你的客戶端擁有了這樣的一個實例對象(如果你new出來一個),它與XMPP協議服務器端的會議不是一個概念。也就是說,當你new出一個這樣的MUCRoom實例對象出來後,會有兩種情況,一是你傳入的參數指明的聊天室如果在服務器中存在,則該實例對象就表示和服務器會議室進行了一個一一對應的關係,你可以通過本地創建的該實例對象,來與服務器中的會議進行會話。而如果你創建的該實例對象在服務器中沒有,則服務器會創建一個這樣的會議出來,並且和第一種情況一樣,你的這個實例對象和服務器的會議進行了映射關係。現在明白了這個MUCRoom類是用來做什麼的了吧,它就是指的本地端的一個和服務器端會議的一個映射關係實例,你可以通過該實例,和服務器端進行會話了。因此當所有的會議成員都和服務器端的這個會議進行會話了,則其就處於一個共同的會議中了。

MUCRoom創建出的實例對象需要管理,因爲你可能會創建多個會議,或者處於多個會議中,比如用一個map結構的容器,其中的key值爲會議編號,而value值則爲會議的實例對象地址。MUCRoom類實例對象中提供了很多的方法,你可以通過其頭文件和源文件看出來其方法的功能來,比如邀請某人進入會議,加入會議,發送消息,銷燬會議等等,具體的情況見源文件說明,這裏重點說一下加入會議時,對應的JID的表現應該如下面示例所示:

       JID nick( “[email protected]/100100”);

上面的myConference指的是個會議名稱,資源名後面的100100指的是你的登陸時的用戶名,這個一定要加上,詳見其API說明。conference.xmpptest見後面的服務發現的說明。

現在有兩種情況需要處理,一是別人可能隨時會邀請你加入至某一會議中,你並沒有在會議室中,另一種情況是如果你處於某一會議中,其他成員可能隨時會在會議室裏發言。下面分別對這兩種情況進行說明。

第一種情況,別人邀請你加入至別人創建的會議中。由於此時並未處於會議中,也不知遠端什麼時候對你進行邀請,所以直觀的感覺應該是做一個線程,再調一個接收這樣的消息的方法,如果有邀請產生,再調用某個接口。而這個接口是誰呢?就是MUCInvitationHandler接口,從字面意思上來說,也是正確的,因爲其就是表明,處理遠端邀請時的處理器,你可以通過繼承(說實現覺得更好一些)這個處理器類,實現其中的:

virtual void handleMUCInvitation( const JID& room, const JID& invitee, const std::string& reason,

         const std::string& body, const std::string& password,

     bool cont )

虛函數,就可以了,比如你可以在實現的這個方法裏做一個提示消息,提示是某某人邀請你參加會議名稱爲某某的會議,當然根據上面的參數可以看出來,如果邀請者設置了會議的密碼,他會將密碼傳過來,你需要做的是如果你願意加入這個會議,需要把密碼也傳入纔可以加入的。

現在有一個問題,就是當你實現了邀請會議處理的接口後,你怎麼把你的這個處理邀請會議的實例對象加入至某一個線程中,讓gloox後臺自動來調用你的實例對象呢?

這時,你想到了一個線程,就是client中的recv函數,該函數會一直接收服務器傳來的消息進行處理,之前我們只處理了文本消息,那是因爲我們只對其中的文本消息感興趣,現在我們不僅對其文本消息感興趣,我們還對邀請這樣的消息感興趣,呵呵,再看看client的方法,查找registerMUCInvitationHandler這個函數,發現client類中沒有這樣的方法,不急,再找一下它的基類,ClientBase,再查找,發現其有這樣的一個方法,說明client類通過繼承ClientBase基類獲得了這樣的一個註冊邀請處事器接口的方法,這時我們就可以調用這個方法,把我們從MUCInvitationHandler接口派生的對象註冊至客戶端實例對象中,這時,如果client實例對象收到了邀請的消息,就會自動調用我們的接口實現了。那麼從另一個角度來說,如果我們不去註冊這樣的接口,也不會有問題的,客戶端對象接收到了邀請的消息,但是判斷MUCInvitationHandler接口爲空,則其扔掉這樣的消息就是。現在我們已經註冊了這個消息了,則其就調用這個接口函數,處理我們的具體的邀請處理了。

第二種情況是處於會議時,會議室成員可能隨時會發送消息至會議室至中,這樣的消息自然又是gloox收到這樣的消息之後,再自動調用某一個接口的實現就可以了,這個接口是誰呢?MUCRoomHandler處理器類,就是爲了完成這樣的事情準備的,從其字面意思可以看出來,它就是處理某一個會議室的各種消息的接口,我們應該實現這個接口中的相應的虛函數,再把我們實現的子類實例加入至你所擁有的MUCRoom類對象中,就可以了。可能我們會有一個疑問,覺得象這種遠端隨時發消息的情況,又是不確定的,需要做一個函數,隨時處於接收狀態纔可以,呵呵,如果你看看MUCRoom的構造函數就明白了。MUCRoom::MUCRoom( ClientBase *parent, const JID& nick, MUCRoomHandler *mrh,

                    MUCRoomConfigHandler *mrch )

這個構造函數的第一個參數就指明瞭連接對象,從這個可以看出,其內部實際上仍是client的對象的recv函數,接收到了會議室的消息之後,來自動調用MUCRoomHandler接口處理器的。只是看起來表現形式不一樣而已,但實質上和處理MUCInvitationHandler接口時情況是一樣的。所以如果一旦我們明白了這個本質的東西,gloox庫的很多相應的類的使用都不成問題了。

現在還有一個功能需要我們熟悉,就是服務查找,因爲你如果需要加入某個會議室的話,你首先需要獲得當前服務器上的所有會議室名稱等相關信息(當然你也可以通過其他途徑獲得你想加入會議室的名稱,比如就在IM消息裏,通過別人告訴你)。那麼,要獲得服務器中的某個服務時,怎麼做呢?

Client對象中有一個disco()方法,這個方法返回一個服務發現的對象地址,然後再調用該服務發現對象中的getDiscoItems()方法,就可以獲得服務發現結果。getDiscoItems()方法的函數原型如下:

void getDiscoItems( const JID& to, const std::string& node, DiscoHandler *dh, int context,

                          const std::string& tid = "" );

這個方法的第一個參數是指的一個JID,它指的是你的服務器上對應的你所要進行服務發現時的服務標識,比如你要獲取會議的消息,則這個JID的表現形式應該是這樣的:JID("conference."+client->server()),在我的這裏,它的可能表現是:conference.xmpptest,要注意兩者中間有個“.”點。其中點的前面部分是指的你的服務類型標識,我用的是openfire服務器,可以在其管理頁面找到你的會議標識ID,我的是conference,你的可能也是這個,可能是其他的,這是可以修改的,只要保證你的客戶端和服務器端的這個會議標識ID一致就可以了。點後面的是你的服務器的電腦名稱(詳情請看gloox前面文章的JID的描述,也可參考XMPP協議中對JID的描述,服務器的地址描述)。服務發現的第二個參數是指的是一個DiscoHandler接口,意思是當其從服務器中獲得了服務發現,就會自動調用這個接口的派生類實現,因爲其是一個接口,不能實例化,只能派生,你需要派生這個接口。第三個參數是一個文檔編號,在這裏你調用時,傳入0即可,在後面的DiscoHandler的虛函數中會傳回(一般沒有用這個)。第四個參數用默認的即可。你需要在你的DiscoHandler接口的實現類中的虛函數中去解析服務發現的結果,handleDiscoItemsResult( Stanza *stanza, int context )中第一個參數中含有你獲得的服務發現,第二個參數就是你前面對應的那個0,一般都沒有用。我們需要去解析傳入的*stanza變量,以獲得我們想要的會議信息。下面是我的一個解析示例,你可以自行處理:

       首先,我的服務器中有一個會議名爲myConference,此時當你調用stanza->xml()時,其XML的表現形式是這樣的:

  <iq type=’result’ id=’uid3’ from=’conference.xmpptest’ to=’100100@xmpptest/spark’><query xmlns=’http://jabber.org/protocol/disco#item’><item jid=’[email protected]’ name=’myConference’/></query></iq>

上面的to中的內容是我的本地的登陸用戶名,是100100item中的jid就是對應的會議ID標識,name中的值就是會議的名稱,我用下面的語句去解析這接收到的XML串:

Tag::TagList quli = stanza->children();

     Tag *queli = *(quli.begin());

     Tag::TagList tli = queli->children();

     Tag::TagList::iterator it = tli.begin();

     for (;it!=tli.end();it++)

     {

         Tag *tag = *it;       

         tag->findAttribute("jid");//這個是你想要的會議室名稱,其他的獲取類似

         //我做的一個容器,用以存儲會議相關的信息,此處略。

     }

最後再說一點,在實際的項目開發中,我們通常還需要對會議進行一個單獨的封裝,比如你做一個CConference的類,裏面提供的是對會議室的各種操作方法,包括剛纔介紹的那兩種接口的實現(我的處理方法是直接通過繼承的方式,讓該會議類來實現前面的兩種接口的虛函數),然後再提供一個接口,參數是Client客戶端實例對象的指針,在實例化該會議類時,調用這個接口,把你開始創建好的Client客戶端實例對象的指針加入至該會議對象中,然後再在該會議對象中把邀請的那個處理器接口的實現註冊至該Client實例對象中。

好了,gloox的東西暫時就告一段落了,我沒有說明比如如何註冊新ID,獲取花名冊等這樣的功能,主要原因是兩方面的,一是我覺得它並不難,如果把我說的這些東西明白了之後。第二個主要原因是我覺得gloox也只是一個做IM的一個類庫,我們在實際的項目應用中,不必將你的項目和這個庫綁得太死,因爲畢竟你的項目中,做業務的事情更重要,而IM只是配合中的一個功能而已,我想你不可能做的項目就是一個IM吧?現在市面上的IM軟件很多,比如QQMSN,等等,他們做得更好一些。而如果你只需要一個IM軟件,建議直接使用openfire+spark的方式,就完全可以了,不用自己去開發,甚至spark還提供了擴展的方式。那麼如果你的項目應用中確實需要註冊(增加新用戶),獲取花名冊這樣的功能怎麼辦,呵呵,我相信如果你的項目中確實有這樣的需求,通常情況下也是有的,比如我的項目中都有。不妨說一下我的處理方法,我會在openfire服務器中預先加入幾百個用戶(做爲公用帳號),然後通過一個分配器,來動態的分配這些帳號給每個MIS系統的中登陸的人,這樣就將IM的帳號和MIS系統中的用戶ID進行綁定了,而在做業務中,我存取的也只是MIS系統中的用戶ID了,而進行IM即時消息時,則是用的登陸時分配的IM公用帳號。這樣的話,當你做加入用戶ID,或者獲取花名冊時,實際上是獲得的MIS系統的帳號ID,而不是IM的帳號,因爲這個帳號本身而言沒有意義。也就是說註冊新MIS系統ID和獲得花名冊這樣的功能,需要我們的項目就用中實現,而這個我想應該不難,相信做MIS系統的人都應該做過增加用戶,獲取用戶信息,獲取所有用戶等等這樣的功能的,實在是很簡單的事情(當然對於需要在席,出席這樣的功能時,處理就稍微麻煩一點)。當然這是我的做法。你也可以通過其他方式來實現,比如註冊時,註冊兩個地方,一個是向IM服務器註冊,另一個是向你的MIS系統註冊。這樣保證了帳號的一致性,也可以實現單點登陸。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章