XMPP在其XEP-0045擴展中定義了一個用於多用戶文本會議(群聊)的協議,類似於聊天室、QQ群等。由於它作為一個標准協議在定義模型上力求完備,涵蓋了現實中的絕大部分IM產品模型,而現實中的IM產品基本都只實現了XMPP定義的模型中的一個子集。
XMPP定義的一些基本概念:
房間:房間的JID標識 <room@service> (例如, <jdev@conference.jabber.org>), 這里 "room" 是房間的名稱而 "service" 是多用戶聊天服務運行所在的主機名
房客:房客的JID標識<room@service/nick>,nick是房客在房間的昵稱
崗位:表達了用戶和房間的長期關系。XMPP定義的崗位有:所有者(owner)、管理者(admin)、成員(member)、排斥者(outcast)
角色:表達了用戶和房間的臨時聯系,它只存在與一次訪問期間。XMPP定義的角色有:主持人(moderator)、與會者(paticipant)、游客(visitor)
有關崗位、角色及其權限詳細描述,參考協議規范描述(角色、崗位和權限)
pom.xml里加入 依賴引用
<dependency> <groupId>tigase</groupId> <artifactId>tigase-muc</artifactId> <version>3.0.0-SNAPSHOT</version> </dependency>
config.tdsl 配置文件中加入配置:
muc(class: tigase.muc.MUCComponent) { defaultRoomConfig { 'tigase#presence_delivery_logic' = 'PREFERE_LAST' 'muc#roomconfig_persistentroom' = 'true' } }
MUC源碼首先從 MUCComponent 這個組件開始分析 :
@Bean(name = "muc", parent = Kernel.class, active = true) @ConfigType(ConfigTypeEnum.DefaultMode) @ClusterModeRequired(active = false) public class MUCComponent extends AbstractKernelBasedComponent {}在
在前一節已說明怎么掃描加載相關的Class,類似這里,加載Kernel.class的時候,會把parent=Kernel.class的Bean全部加載到容器中
MUCComponent 也實現了RegistrarBean,在初始化的時候,會被調用 public void register(Kernel kernel) {} 函數進行注冊工作
public abstract class AbstractKernelBasedComponent extends AbstractMessageReceiver implements XMPPService, DisableDisco, RegistrarBean {}
由於用戶客戶端發來的MUC消息協議為:to=<room@service/nick> 例如 llooper@muc.llooper/spark ,目的地上直接表示到muc.組件上,所以MUC模塊入口就是在MUCComponent 類processPacket(Packet packet) 方法上
@Override public void processPacket(Packet packet) { stanzaProcessor.processPacket(packet); }
public void processPacket(Packet packet) { if (log.isLoggable(Level.FINER)) { log.finer("Received: " + packet.getElement()); } //查找是否為Response模式,則由Handler來處理 Runnable responseHandler = responseManager.getResponseHandler(packet); boolean handled; if (responseHandler != null) { handled = true; responseHandler.run(); } else { //否則調用處理包的函數 handled = this.process(packet); } if (!handled) { final String t = packet.getElement().getAttributeStaticStr(Packet.TYPE_ATT); final StanzaType type = (t == null) ? null : StanzaType.valueof(t); if (type != StanzaType.error) { throw new ComponentException(Authorization.FEATURE_NOT_IMPLEMENTED); } else { if (log.isLoggable(Level.FINER)) { log.finer(packet.getElemName() + " stanza with type='error' ignored"); } } } 。。。。。。 }
private boolean process(final Packet packet) throws ComponentException, TigaseStringprepException { boolean handled = false; if (log.isLoggable(Level.FINER)) { log.finest("Processing packet: " + packet.toString()); } //找到匹配的Module來處理 for (Module module : this.modules) { if (module.canHandle(packet)) { handled = true; if (log.isLoggable(Level.FINER)) { log.finer("Handled by module " + module.getClass()); } module.process(packet); if (log.isLoggable(Level.FINEST)) { log.finest("Finished " + module.getClass()); } } } return handled; }
例如MUC中實現的Module有如下:
這里的邏輯比較簡單,也就是根據包的節類型等信息,來匹配感興趣的處理Module,來進行處理
再來看下 module.canHandle(packet) 是怎么樣找到匹配的Module來處理的:
default boolean canHandle(Packet packet) { Criteria criteria = getModuleCriteria(); return criteria != null && criteria.match(packet.getElement()); }
private final String[] names;
private final String[] xmlns;
@Override public boolean ElemPathCriteria.match(Element element) { //判斷<iq> packet的名字如iq是否和本module中定義name一致 boolean match = element.getName().equals(names[0]); if (match && xmlns[0] != null) {
//並且判斷命名空間是否一致 match &= xmlns[0].equals(element.getXMLNS()); } Element child = element; int i = 1;
//例如packet <iq><set>xxx</set></iq> 則name為數組{"iq","set"},前面判斷了iq,這里判斷set是否一致,如果還存在子節點,則依次比較是否一致 for (; i < names.length; i++) { String n = names[i]; String x = xmlns[i]; // child = child.getChildStaticStr(n, x); match &= child != null; if (!match) { return match; } } // TODO Auto-generated method stub return match; }
例如針對出muc的 <presence> 由 PresenceModuleImpl 進行業務邏輯處理
@Bean(name = PresenceModuleImpl.ID, parent = MUCComponent.class, active = true) public class PresenceModuleImpl extends AbstractMucModule implements PresenceModule { protected static final Logger log = Logger.getLogger(PresenceModule.class.getName()); private static final Criteria CRIT = ElementCriteria.name("presence"); }