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");
}
