MUC 簡述
其實想了解一個框架最好的方法就是去閱讀官方文檔怔接,下面會分別給出中英文檔的連接骑篙。(當然如果你沒興趣看文檔的話靶端,也可以直接略過看我提煉出來比較重要的幾個概念)
XEP-0045: 多用戶聊天
XEP-0045: Multi-User Chat
角色(Roles) 和 崗位(Affiliations)
這兩個概念是基于一個用戶在一個聊天室內擁有的權限所提出來的杨名,但倆者對應的權限和時效性是有區(qū)別的须喂。
角色(Roles)
Roles are temporary in that they do not necessarily persist across a user's visits to the room and MAY change during the course of an occupant's visit to the room. An implementation MAY persist roles across visits and SHOULD do so for moderated rooms (since the distinction between visitor and participant is critical to the functioning of a moderated room).
簡單來說意思就是 角色 是動態(tài)變化的坞生。隨著 加入 或者 離開 聊天室這些行為,用戶的 角色 也會發(fā)生變化卒废。
下面是所有已經定義的角色:
- 主持人(Moderator): 聊天室中權限最高的角色
- 與會者(Participant): 聊天室的主要參與人
- 游客(Visitor): 聊天室的瀏覽者
- 無角色(None): 未加入聊天室的用戶
以下是四種角色對應的具體權限:
權限 | 無 | 游客 | 與會者 | 主持人 |
---|---|---|---|---|
在聊天室中出席 | 無 | 有 | 有 | 有 |
接受消息 | 無 | 有 | 有 | 有 |
接收/廣播 出席信息 | 無 | 有 | 有 | 有 |
改變可用性狀態(tài) | 無 | 有 | 有 | 有 |
改變聊天室昵稱 | 無 | 有 | 有 | 有 |
發(fā)送私人消息 | 無 | 有 | 有 | 有 |
邀請用戶 | 無 | 有 | 有 | 有 |
發(fā)送公開消息 | 無 | 無 | 有 | 有 |
修改標題 | 無 | 無 | 有 | 有 |
踢出用戶 | 無 | 無 | 無 | 有 |
授予發(fā)言權 | 無 | 無 | 無 | 有 |
撤銷發(fā)言權 | 無 | 無 | 無 | 有 |
注: 所有對權限的操作均無法對平級用執(zhí)行
崗位(Affiliations)
雖然國內大部分都將 Affiliations 翻譯成 崗位 ,但我個人覺得如果符合中文的理解电谣,主持人 豈不是更符合 崗位 的字面意思辰企。當然這里只是抱怨一下镐捧,我還是會沿用這一翻譯臭增,避免與其它文章中叫法不一給讀者造成閱讀上的障礙列牺。
大家可以把 聊天室 看成是一個俱樂部瞎领,由以下幾種 崗位 組成:
- 所有者(Owner): 一般為聊天室創(chuàng)建者
- 管理員(Admin): 所有者可授予其他成員
- 會員(Member): 聊天室 的正式成員
- 排斥者(Outcast): 簡單來說就是黑名單九默,排斥者無法進入 聊天室
崗位 相比起 角色 來說是靜態(tài)的身份宾毒,不會隨著用戶上下線诈铛,進入退出聊天室而變動,只有 所有者 或 管理員 主動修改用戶的 崗位
崗位 相關的權限如下:
權限 | Outcast(被排斥者) | None(無) | Member(成員) | Admin(管理員) | Owner(所有者) |
---|---|---|---|---|---|
進入聊天室 | 否 | 是 | 是 | 是 | 是 |
注冊一個開放的聊天室 | 否 | 是 | N/A | N/A | N/A |
接收成員列表 | 否 | 否 | 是 | 是 | 是 |
加入一個僅限會員的聊天室 | 否 | 否 | 是 | 是 | 是 |
禁止成員并把用戶的崗位刪除 | 否 | 否 | 否 | 是 | 是 |
編輯成員列表 | 否 | 否 | 否 | 是 | 是 |
編輯主持人列表 | 否 | 否 | 否 | 是 | 是 |
編輯管理員列表 | 否 | 否 | 否 | 否 | 是 |
編輯所有者列表 | 否 | 否 | 否 | 否 | 是 |
變更聊天室定義 | 否 | 否 | 否 | 否 | 是 |
銷毀聊天室 | 否 | 否 | 否 | 否 | 是 |
聊天室類型
用戶創(chuàng)建完聊天室后可以選擇性配置聊天室或者使用默認配置耳峦,不同的配置參數(shù)則會使聊天室類型發(fā)生改變。
下面關于不同類型聊天室的區(qū)別摘自 XEP-0045: 多用戶聊天
Hidden Room(隱藏聊天室) : 一個無法被任何用戶以普通方法如搜索和服務查詢來發(fā)現(xiàn)的聊天室; 反義詞: 公開(public)聊天室.
Members-Only Room(僅限會員的聊天室) : 如果一個用戶不在成員列表中則無法加入的一個聊天室; 反義詞: 開放(open)聊天室.
Moderated Room(被主持的聊天室) : 只有有"發(fā)言權"的用戶才可以發(fā)送消息給所有房客的聊天室; 反義詞: 非主持的(Unmoderated)聊天室.
Non-Anonymous Room(非匿名聊天室) : 一個房客的全JID會暴露給所有其他房客的聊天室, 盡管房客可以選擇任何期望的聊天室昵稱; 相對的是半匿名(Semi-Anonymous)聊天室.
Open Room(開放聊天室) : 任何人可以加入而不需要在成員列表中的聊天室; 反義詞: 僅限會員的聊天室.
Password-Protected Room(密碼保護聊天室) : 一個用戶必須提供正確密碼才能加入的聊天室; 反義詞: 非保密聊天室.
Persistent Room(持久聊天室) : 如果最后一個房客退出也不會被銷毀的聊天室; 反義詞: 臨時聊天室.
Public Room(公開聊天室) : 用戶可以通過普通方法如搜索和服務查詢來發(fā)現(xiàn)的聊天室; 反義詞: 隱藏聊天室.
Semi-Anonymous Room(半匿名聊天室) : 一個房客的全JID只能被聊天室管理員發(fā)現(xiàn)的聊天室; 相對的是非匿名(Non-Anonymous)聊天室.
Temporary Room(臨時聊天室) : 如果最后一個房客退出就會被銷毀的聊天室; 反義詞: 持久聊天室.
Unmoderated Room(非主持的聊天室) : 任何房客都被允許發(fā)送消息給所有房客的聊天室; 反義詞: 被主持的聊天室.
Unsecured Room(非保密聊天室) : 任何人不需要提供密碼就可以進入的聊天室; 反義詞: 密碼保護聊天室.
不同的 聊天室類型 針對不同 崗位 的用戶進入聊天室時會設置一個默認的角色妨退,具體如下表:
聊天室類型 \ 崗位 | 無 | 會員 | 管理員 | 所有者 |
---|---|---|---|---|
被主持的聊天室 | 游客 | 與會者 | 主持人 | 主持人 |
非主持的聊天室 | 與會者 | 與會者 | 主持人 | 主持人 |
僅會員加入的聊天室 | (無法加入聊天室) | 與會者 | 主持人 | 主持人 |
開放聊天室 | 與會者 | 與會者 | 主持人 | 主持人 |
MUC 實操
在把基本的概念介紹一遍后妇萄,我們終于要開始 Code 環(huán)節(jié)啦蜕企!
MUC 初始化
1、首先還是得在我們的 XMPPStream
初始化時加入 MUC
模塊
- (void)initalize
{
// 初始化連接
_xmppStream = [[XMPPStream alloc] init];
[_xmppStream setHostName:XMPP_HOST];
[_xmppStream setHostPort:XMPP_PORT];
[_xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[_xmppStream setKeepAliveInterval:30];
[_xmppStream setEnableBackgroundingOnSocket:YES];
.....
// 接入群聊模塊
_xmppMuc = [[XMPPMUC alloc] init];
[_xmppMuc addDelegate:self delegateQueue:dispatch_get_main_queue()];
[_xmppMuc activate:_xmppStream];
}
2冠句、在登錄成功后轻掩,調用 discoverServices
來發(fā)現(xiàn)群聊服務
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
NSLog(@"Authenticate Success !");
// 發(fā)送上線消息
[self goOnline];
// 啟用流管理
[_xmppStreamManagement enableStreamManagementWithResumption:YES maxTimeout:0];
// 獲取群列表
[self.xmppMuc discoverServices];
// 發(fā)送 xmpp 登錄成功通知
[[NSNotificationCenter defaultCenter] postNotificationName:XMPPLoginSuccess object:nil];
}
3、實現(xiàn) MUC
模塊的代理方法
/** 成功發(fā)現(xiàn) xmpp 服務器上的 服務地址 */
- (void)xmppMUC:(XMPPMUC *)sender didDiscoverServices:(NSArray *)services
{
NSLog(@"Discover Services Success : %@", services);
// 取出多人聊天服務地址
DDXMLElement *item = [services firstObject];
// 查找聊天室
_roomService = [[item attributeForName:@"jid"] stringValue];
[_xmppMuc discoverRoomsForServiceNamed:_roomService];
}
/** 發(fā)現(xiàn) xmpp 服務器上的 服務地址失敗 */
- (void)xmppMUCFailedToDiscoverServices:(XMPPMUC *)sender withError:(NSError *)error
{
NSLog(@"Discover Services Failed : %@", error);
}
/** 成功獲取到 serviceName 服務器上的聊天室集 */
- (void)xmppMUC:(XMPPMUC *)sender didDiscoverRooms:(NSArray *)rooms forServiceNamed:(NSString *)serviceName
{
}
/** 獲取 serviceName 服務器上的聊天室集失敗 */
- (void)xmppMUC:(XMPPMUC *)sender failedToDiscoverRoomsForServiceNamed:(NSString *)serviceName withError:(NSError *)error
{
NSLog(@"Discover Rooms Failed : %@", error);
}
這里對于 [self.xmppMuc discoverServices];
方法還是有一點說明的。在實際開發(fā)中可以省略此步驟讓后臺直接告知 多人聊天服務地址 然后直接調用 [_xmppMuc discoverRoomsForServiceNamed:_roomService]
去查詢聊天室集即可。
MUC 創(chuàng)建聊天室
1、發(fā)送創(chuàng)建聊天室請求
/**
請求創(chuàng)建一個聊天室
@param roomName 聊天室名稱
@param createHandle 創(chuàng)建回調
@return 請求是否發(fā)送
*/
- (BOOL)createRoomWithName:(NSString *)roomName
handle:(MucCreateHandle)createHandle
{
if (_createHandle) {
return NO;
}
_roomName = roomName;
_createHandle = createHandle;
NSString *roomId = [_xmppStream generateUUID];
XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@", roomId, _roomService]];
XMPPRoomMemoryStorage *roomStorage = [[XMPPRoomMemoryStorage alloc] init];
XMPPCustomRoom *room = [[XMPPCustomRoom alloc] initWithRoomStorage:roomStorage
jid:jid
dispatchQueue:dispatch_get_main_queue()];
[room addDelegate:self delegateQueue:dispatch_get_main_queue()];
[room activate:_xmppStream];
[room createRoomUsingName:_myJID.user password:nil];
return YES;
}
2课蔬、實現(xiàn) XMPPRoom
代理、并發(fā)送配置信息
- (void)sendDefaultRoomConfig:(XMPPRoom *)room
{
DDXMLElement *x = [DDXMLElement elementWithName:@"x" xmlns:@"jabber:x:data"];
// 配置聊天室名稱
DDXMLElement *nameField = [DDXMLElement elementWithName:@"field"];
[nameField addAttributeWithName:@"var" stringValue:@"muc#roomconfig_roomname"];
DDXMLElement *nameValue = [DDXMLElement elementWithName:@"value" stringValue:_roomName];
[nameField addChild:nameValue];
[x addChild:nameField];
// 配置聊天室永久存在
DDXMLElement *exitField = [DDXMLElement elementWithName:@"field"];
[exitField addAttributeWithName:@"var" stringValue:@"muc#roomconfig_persistentroom"];
DDXMLElement *exitValue = [DDXMLElement elementWithName:@"value" stringValue:@"1"];
[exitField addChild:exitValue];
[x addChild:exitField];
[room configureRoomUsingOptions:x];
_roomName = @"";
}
#pragma mark - room delegate
/** 成功創(chuàng)建聊天室 */
- (void)xmppRoomDidCreate:(XMPPRoom *)sender
{
// 1. 發(fā)送默認配置
[self sendDefaultRoomConfig:sender];
// 2. 成功回調 (為了確保 UI 刷新時已經獲取到了聊天室信息襟锐,回調放在 xmppRoom:didFetchInfoList: 中執(zhí)行
}
/** 創(chuàng)建聊天室失敗 */
- (void)xmppRoom:(XMPPRoom *)sender didFailToCreate:(NSError *)error
{
if (_createHandle) {
_createHandle(NO, nil);
_createHandle = nil;
}
}
/** 成功配置聊天室信息 */
- (void)xmppRoom:(XMPPRoom *)sender didFetchInfoList:(DDXMLElement *)identity
{
if (_createHandle) {
_createHandle(YES, sender);
_createHandle = nil;
}
if (_joinHandle) {
_joinHandle(YES);
_joinHandle = nil;
}
}
在這里有一點需要說明下:
配置聊天室屬性時如果不設置為永久聊天室的話,聊天室會在所有人離開聊天室( 用戶下線也被視為離開聊天室 )后自動銷毀。下次調用 [_xmppMuc discoverRoomsForServiceNamed:_roomService]
方法獲得的聊天室集中將不會有此聊天室
MUC 接受邀請加入议双、邀請用戶加入 聊天室
接受聊天室邀請并加入聊天室
- (void)joinRoomWithJid:(NSString *)roomJid
nickName:(NSString *)nickName
handle:(MucJoinHandle)joinHandle
{
DLog(@"XMPPJID = %@",roomJid);
_joinHandle = joinHandle;
XMPPJID *roomJID = [XMPPJID jidWithString:roomJid];
XMPPRoomMemoryStorage *roomStorage = [[XMPPRoomMemoryStorage alloc] init];
XMPPRoom *xmppRoom = [[XMPPRoom alloc]
initWithRoomStorage:roomStorage
jid:roomJID
dispatchQueue:dispatch_get_main_queue()];
[xmppRoom activate:[self xmppStream]];
[xmppRoom addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppRoom joinRoomUsingNickname:nickName history:nil];
}
/** MUC 代理 */
/** 此方法在收到邀請時會調用 */
- (void)xmppMUC:(XMPPMUC *)sender roomJID:(XMPPJID *)roomJID didReceiveInvitation:(XMPPMessage *)message
{
// 解析消息類型
DDXMLElement * x = [message elementForName:@"x" xmlns:XMPPMUCUserNamespace];
DDXMLElement * invite = [x elementForName:@"invite"];
if (invite != nil)
{
// 確定為邀請信息后逾礁,解析聊天室 JID
NSString *nickName = _myJID.user;
NSString *roomJid = [[message attributeForName:@"from"] stringValue];
// 加入聊天室
[self joinRoomWithJid:roomJid nickName:nickName handle:nil];
}
}
邀請用戶加入聊天室
- (void)inviteFriends:(XMPPJID *)jid
{
// _room 為要邀請的房間 [_room class] 為 XMPPRoom
[_room inviteUser:jid withMessage:@"happy together !"];
}
MUC 發(fā)送/接受聊天室會話
說到發(fā)送聊天室會話债热,不知道大家是否還記得私聊會話的發(fā)送
/** 當 toJID 為 XMPPRoom 的 JID 和 type 為 @"groupchat" 時配并,發(fā)送的此條會話即為聊天室會話 */
- (void)sendMessage:(NSString *)body
to:(XMPPJID *)toJID
type:(NSString *)type
extend:(DDXMLElement *)extend
statusHandle:(MessageStatusHandle)statusHandle
{
NSString *uuId = [_xmppStream generateUUID];
XMPPMessage *message = [XMPPMessage messageWithType:type to:toJID];
[message addBody:body];
[message addAttributeWithName:@"id" stringValue:uuId];
// 生成時間戳
NSDate *now = [NSDate date];
NSInteger since1970 = [now timeIntervalSince1970];
NSString *dateString = [NSString stringWithFormat:@"%@", @(since1970)];
// 添加擴展
DDXMLElement *external = [DDXMLElement elementWithName:@"demoExternal"];
DDXMLElement *version = [DDXMLElement elementWithName:@"demoVersion" stringValue:IM_VERSION];
DDXMLElement *avatar = [DDXMLElement elementWithName:@"avatar" stringValue:getValueByKey(LOGOURL)];
DDXMLElement *nickName = [DDXMLElement elementWithName:@"nickName" stringValue:getValueByKey(NICKNAME)];
DDXMLElement *date = [DDXMLElement elementWithName:@"date" stringValue:dateString];
[external addChild:avatar];
[external addChild:version];
[external addChild:nickName];
[external addChild:date];
if (extend) {
[external addChild:extend];
}
[message addChild:external];
[_xmppStream sendElement:message];
[_statusHandleDic setObject:statusHandle forKey:uuId];
}
我們有兩種方式獲取到聊天室會話
// xmppstream delegate
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
if ([message isGroupChatMessageWithBody])
{
// 此處為聊天室會話消息
}
}
// xmpproom delegate
- (void)xmppRoom:(XMPPRoom *)sender didReceiveMessage:(XMPPMessage *)message fromOccupant:(XMPPJID *)occupantJID
{
// 此處為 sender 聊天室內的會話消息
}
好了,雖然有些細節(jié)沒有說但一個完整的多人聊天步驟基本上就是個樣子迄委。一些真實開發(fā)中我遇到的坑和細節(jié)會在下一篇中講解信轿。
最后祝大家 have fun !