当前位置: 首页 > 工具软件 > gloox > 使用案例 >

gloox实现会议服务功能

宓茂才
2023-12-01

通过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( “myConference@conference.xmpptest/100100”);

上面的myConference指的是个会议名称,资源名后面的100100指的是你的登陆时的用户名,这个一定要加上,详见其API说明。conference.xmpptest见后面的服务发现的说明。

现在有两种情况需要处理,一是别人可能随时会邀请你加入至某一会议中,你并没有在会议室中,另一种情况是如果你处于某一会议中,其他成员可能随时会在会议室里发言。下面分别对这两种情况进行说明。

第一种情况,别人邀请你加入至别人创建的会议中。由于此时并未处于会议中,也不知远端什么时候对你进行邀请,所以直观的感觉应该是做一个线程,再调一个接收这样的消息的方法,如果有邀请产生,再调用某个接口。而这个接口是谁呢?就是MUCInvitationHandler接口,从字面意思上来说,也是正确的,因为其就是表明,处理远端邀请时的处理器,你可以通过继承(说实现觉得更好一些)这个处理器类,实现其中的:

virtual void handleMUCInvitationconst JIDroomconst JIDinviteeconst std::stringreason,

         const std::stringbodyconst std::stringpassword,

     bool cont )

虚函数,就可以了,比如你可以在实现的这个方法里做一个提示消息,提示是某某人邀请你参加会议名称为某某的会议,当然根据上面的参数可以看出来,如果邀请者设置了会议的密码,他会将密码传过来,你需要做的是如果你愿意加入这个会议,需要把密码也传入才可以加入的。

现在有一个问题,就是当你实现了邀请会议处理的接口后,你怎么把你的这个处理邀请会议的实例对象加入至某一个线程中,让gloox后台自动来调用你的实例对象呢?

这时,你想到了一个线程,就是client中的recv函数,该函数会一直接收服务器传来的消息进行处理,之前我们只处理了文本消息,那是因为我们只对其中的文本消息感兴趣,现在我们不仅对其文本消息感兴趣,我们还对邀请这样的消息感兴趣,呵呵,再看看client的方法,查找registerMUCInvitationHandler这个函数,发现client类中没有这样的方法,不急,再找一下它的基类,ClientBase,再查找,发现其有这样的一个方法,说明client类通过继承ClientBase基类获得了这样的一个注册邀请处事器接口的方法,这时我们就可以调用这个方法,把我们从MUCInvitationHandler接口派生的对象注册至客户端实例对象中,这时,如果client实例对象收到了邀请的消息,就会自动调用我们的接口实现了。那么从另一个角度来说,如果我们不去注册这样的接口,也不会有问题的,客户端对象接收到了邀请的消息,但是判断MUCInvitationHandler接口为空,则其扔掉这样的消息就是。现在我们已经注册了这个消息了,则其就调用这个接口函数,处理我们的具体的邀请处理了。

第二种情况是处于会议时,会议室成员可能随时会发送消息至会议室至中,这样的消息自然又是gloox收到这样的消息之后,再自动调用某一个接口的实现就可以了,这个接口是谁呢?MUCRoomHandler处理器类,就是为了完成这样的事情准备的,从其字面意思可以看出来,它就是处理某一个会议室的各种消息的接口,我们应该实现这个接口中的相应的虚函数,再把我们实现的子类实例加入至你所拥有的MUCRoom类对象中,就可以了。可能我们会有一个疑问,觉得象这种远端随时发消息的情况,又是不确定的,需要做一个函数,随时处于接收状态才可以,呵呵,如果你看看MUCRoom的构造函数就明白了。MUCRoom::MUCRoomClientBase *parentconst JIDnickMUCRoomHandler *mrh,

                    MUCRoomConfigHandler *mrch )

这个构造函数的第一个参数就指明了连接对象,从这个可以看出,其内部实际上仍是client的对象的recv函数,接收到了会议室的消息之后,来自动调用MUCRoomHandler接口处理器的。只是看起来表现形式不一样而已,但实质上和处理MUCInvitationHandler接口时情况是一样的。所以如果一旦我们明白了这个本质的东西,gloox库的很多相应的类的使用都不成问题了。

现在还有一个功能需要我们熟悉,就是服务查找,因为你如果需要加入某个会议室的话,你首先需要获得当前服务器上的所有会议室名称等相关信息(当然你也可以通过其他途径获得你想加入会议室的名称,比如就在IM消息里,通过别人告诉你)。那么,要获得服务器中的某个服务时,怎么做呢?

Client对象中有一个disco()方法,这个方法返回一个服务发现的对象地址,然后再调用该服务发现对象中的getDiscoItems()方法,就可以获得服务发现结果。getDiscoItems()方法的函数原型如下:

void getDiscoItemsconst JIDtoconst std::stringnodeDiscoHandler *dhint context,

                          const std::stringtid = "" );

这个方法的第一个参数是指的一个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=’myconference@conference.xmpptest’ name=’myConference’/></query></iq>

上面的to中的内容是我的本地的登陆用户名,是100100,item中的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软件很多,比如QQ,MSN,等等,他们做得更好一些。而如果你只需要一个IM软件,建议直接使用openfire+spark的方式,就完全可以了,不用自己去开发,甚至spark还提供了扩展的方式。那么如果你的项目应用中确实需要注册(增加新用户),获取花名册这样的功能怎么办,呵呵,我相信如果你的项目中确实有这样的需求,通常情况下也是有的,比如我的项目中都有。不妨说一下我的处理方法,我会在openfire服务器中预先加入几百个用户(做为公用帐号),然后通过一个分配器,来动态的分配这些帐号给每个MIS系统的中登陆的人,这样就将IM的帐号和MIS系统中的用户ID进行绑定了,而在做业务中,我存取的也只是MIS系统中的用户ID了,而进行IM即时消息时,则是用的登陆时分配的IM公用帐号。这样的话,当你做加入用户ID,或者获取花名册时,实际上是获得的MIS系统的帐号ID,而不是IM的帐号,因为这个帐号本身而言没有意义。也就是说注册新MIS系统ID和获得花名册这样的功能,需要我们的项目就用中实现,而这个我想应该不难,相信做MIS系统的人都应该做过增加用户,获取用户信息,获取所有用户等等这样的功能的,实在是很简单的事情(当然对于需要在席,出席这样的功能时,处理就稍微麻烦一点)。当然这是我的做法。你也可以通过其他方式来实现,比如注册时,注册两个地方,一个是向IM服务器注册,另一个是向你的MIS系统注册。这样保证了帐号的一致性,也可以实现单点登陆。

 类似资料: