XMPP學習記錄之實戰篇
在學習iOS以來一直想要研究即時聊天方面的技術,無奈工作或時間原因一直擱淺此計劃,近日偷得時閑開始著手與XMPP的學習。在學習之前我一直認為XMPP對我來說是一個很有技術的挑戰,在了解了協議的具體形式后,才發覺其實技術的難度只在跟你底層代碼原理的掌握程度的熟練度有關,說通俗一點,很多東西其實我們都會,只是在各個框架或技術中我們沒有考慮到的東西別人都考慮周全!比如你若對socket有一定的了解并懂得xml數據解析那你就可以看懂大部分的xmpp文檔!所以只要掌握了相對來說底層的一些技術那么對于學習于此技術相關的東西都會變得輕松起來。
在開始學習XMPP之前希望各位朋友首先對 socket和tcp 有一定的了解,會使用coreData和xml解析和搭建XMPP相關服務器,這樣學習起來就更為簡單!因為xmpp的數據庫使用的是coreData,數據傳輸協議用的是xml格式。如對socket和tcp不太了解,可以參考我之前寫的博客。
1.XMPP核心類(XMPPStream)
XMPPStream * _xmppStream , 其作用與socket相同,用于創建客戶端與服務器端的鏈接,并能后對流事件進行監聽獲得其流事件,在類對象可以設置異步隊列。這樣就可將一些耗時操作放到后臺進行。另外xmpp使用模塊功能, 所有的模塊的激活使用都需要關聯_xmppStream對象
1.1登錄
在初始化好_xmppStream后,設置其代理,并進行鏈接,在socket進行服務器鏈接時會要求輸入ip地址和端口號,而_xmppStream也是大體相同,讓我們來看看代碼
//1.配置xmppStream信息
//創建xmpp流
_xmppStream = [[XMPPStream alloc] init];
//添加代理及隊列
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
//2.設置用戶登錄信息
//其中第一參數為用戶id,domain為服務器域名,resource為手機類型
XMPPJID * userJid = [XMPPJID jidWithUser:@"用戶id" domain:@"服務器域名" resource:@"iphone"];
//3.配置xmppstream的用戶信息,ip及端口號(xmpp默認端口號都為5222)
_xmppStream.myJID = userJid;
//此處使用自己主機作為服務器
_xmppStream.hostName = @"127.0.0.1";
_xmppStream.hostPort = 5222;
//4.與服務器進行鏈接
//發起鏈接,此處超過20秒后會回調鏈接失敗方法,若成功則會調用鏈接成功方法
NSError *error;
[_xmppStream connectWithTimeout:20 error:&error];
if (error != nil) {
BQLog(@"發起鏈接失敗:%@",error.localizedDescription);
} 當鏈接到主機成功后,需要驗證密碼,此時用戶需要將密碼發送到服務器進行驗證,當驗證成功后用戶則登錄成功,但在登錄成功時,用戶還是處于離線狀態,需要想服務器發送在線信息,更新個人狀態
/** 鏈接到主機 */
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
BQLog(@"鏈接成功");
//鏈接到主機后需要發送密碼才能進行登錄
[self sendPwdToHost];
}
/** 鏈接到主機超時 */
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
BQLog(@"鏈接主機超時");
}
/** 驗證密碼*/
- (void)sendPwdToHost {
NSError *error;
//驗證密碼
[_xmppStream authenticateWithPassword:[[NSUserDefaults standardUserDefaults] objectForKey:PASS_WORD] error:&error];
if (error != nil) {
BQLog(@"信息發送失敗%@",error.localizedDescription);
}
}
/** 密碼驗證成功 */
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
BQLog(@"登錄成功");
//登錄成功后需要向服務器發送在線狀態
[self sendOnlineToHost];
}
/** 密碼驗證失敗 */
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error {
BQLog(@"登錄失敗:%@",error);
}
/** 向服務器發送在線狀態 */
- (void)sendOnlineToHost {
//在線類,(XMPPPresence為DDXMLElement子類)
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
//發送在線信息到服務器
[_xmppStream sendElement:presence];
} 1.2注冊
之所以將注冊放到登錄后面來講是因為注冊所用代碼與登錄相差無幾, 唯一的區別就是將驗證密碼改為注冊密碼 ,當信息注冊好后再執行登錄步驟即可
- (void)sendPwdToHost {
NSError *error;
//進行密碼注冊,密碼發送成功后會回調下面2個方法
[_xmppStream registerWithPassword:[[NSUserDefaults standardUserDefaults] objectForKey:REGISTER_PWD] error:&error];
if (error != nil) {
BQLog(@"密碼發送失敗%@",error.localizedDescription);
}
}
/** 注冊成功調用*/
- (void)xmppStreamDidRegister:(XMPPStream *)sender{
BQLog(@"用戶名注冊成功");
}
/** 注冊失敗調用*/
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
BQLog(@"用戶名注冊失敗");
} 2.電子名片模塊(包含頭像模塊)
2.1電子名片模塊配置與激活
電子名片模塊在xmpp框架下拓展文件XEP-0054當中,需要導入所需頭文件,再 配置好模塊后利用_xmppStream將其激活 才可使用。其中_vCardStorage是xmpp所做的本地緩存處理。對于這個緩存處理就不多說了,各位朋友應該知道其具體作用的。頭像模塊的作用會在后續的文章中用到。此處不會涉及
//創建電子名片對象存取器
_vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
//創建電子名片對象
_vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];
//電子名片需要被激活(電子名片一般配合頭像模塊一起使用)
[_vCard activate:_xmppStream];
//頭像模塊
_avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];
//激活頭像模塊
[_avatar activate:_xmppStream]; 2.2電子名片的讀取更新
在激活這些模塊后就可以異步讀取到個人電子名片信息了。接下來便是個人電子名片信息的更新,此處較為簡單,沒有什么思路便直接上代碼。讀取的話當然是直接獲取myvCaed值即可。此處需要注意的是由于xmpp協議傳輸格式都為xml數據格式,部分節點可能沒有解析到,那就需要自己去解析獲取。
//電子名片信息緩存(個人理解為數據庫的內容) ,這里的[[BQXMPPTool sharedXMPPTool].vCardvCard就是上方所羅列的_vCard
XMPPvCardTemp *myvCard = [BQXMPPTool sharedXMPPTool].vCard.myvCardTemp;
//以下為設置其具體信息
if (self.headImageView != nil) {
//此處圖片數據較大只是簡單處理以下
myvCard.photo = UIImageJPEGRepresentation(self.headImageView.image, 0.5);
}
myvCard.nickname = self.nicknameLabel.text;
myvCard.orgName = self.departmentLabel.text;
myvCard.title = self.positionLabel.text;
myvCard.note = self.phoneLabel.text;
myvCard.mailer = self.emailLabel.text;
//更新電子名片信息到服務器
[[BQXMPPTool sharedXMPPTool].vCard updateMyvCardTemp:myvCard]; 3.花名冊(好友)模塊
3.1花名冊模塊配置與激活
//創建花名冊存儲器
_rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
//創建花名冊模塊
_roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];
//激活花名冊模塊
[_roster activate:_xmppStream]; 3.2獲取好友列表和信息
所有添加過的好友信息都存儲在花名冊存儲器當中,此時我們需要根據花名冊存儲器的上下文找到對應數據庫并查找其中的好友資料,所有的好友都有訂閱狀態('none','from','to','both'),我們需要的是已添加和訂閱的好友,所以需要進行好友過濾,最后可以利用NSFetchedResultsController控制器來實時監聽數據庫內容。當數據庫內容有任何改變時都會調用其回調方法,在回調方法里我們便可重新刷新界面顯示最新的好友列表,所有的好友信息都在控制器結果中以XMPPUserCoreDataStorageObject類對象來表示。
//1.配置花名冊數據庫上下文(2種方式)
_rosterContext = [BQXMPPTool sharedXMPPTool].rosterStorage.mainThreadManagedObjectContext;
//2.從上下文中取出對應的模型
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPUserCoreDataStorageObject"];
//3.設置結果排序規則
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES];
request.sortDescriptors = @[sort];
//3.1利用條件篩選,過濾
NSPredicate *predic = [NSPredicate predicateWithFormat:@"subscription != %@",@"none"];
request.predicate = predic;
//4.根據規則配置對應數據庫中的數據存取器
_resultsCtl = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:_rosterContext sectionNameKeyPath:nil cacheName:nil];
//5.利用獲取數據
NSError *error;
[_resultsCtl performFetch:&error];
if (error) {
BQLog(@"%@",error);
return;
}
//6.設置代理(配置數據存取器后,每當數據有改變時都會回調方法)
_resultsCtl.delegate = self;
//取得對應好友的用戶信息,此處展示第一個好友信息
XMPPUserCoreDataStorageObject *user = _resultsCtl.fetchedObjects[0];
//此處就可利用頭像模塊取出好友頭像
NSData *imageData = [[BQXMPPTool sharedXMPPTool].avatar photoDataForJID:user.jid]; 3.3好友的添加,驗證與刪除
好友的添加(訂閱)主要分三步: 1. 輸入 jid(XMPPJid) 2. 判斷是否存在此好友 3. 沒有的話 添加 ( 訂閱 ) 好友,當接受到好友的添加后代理方法里會有接受到好友訂閱信息的回調,我們只需要在里面來驗證好友即可,關于好友的刪除和訂閱是成對的,相當于將訂閱改為刪除即可。下面實現代碼
//先來訂閱(添加)好友
//拼接實際的用戶id(實際id由用戶名加域名組成)
NSString *strJid = [NSString stringWithFormat:@"%@@%@",self.textField.text,[BQXMPPTool sharedXMPPTool].hostName];
XMPPJID *jid = [XMPPJID jidWithString:strJid];
//判斷是否存在此好友
BOOL hasFirend = [[BQXMPPTool sharedXMPPTool].rosterStorage userExistsWithJID:jid xmppStream:[BQXMPPTool sharedXMPPTool].xmppStream];
if (hasFirend == YES || [self.textField.text isEqualToString:[[NSUserDefaults standardUserDefaults] objectForKey:USER_NAME]]) {
//列表中若存在此好友給予提示
NSString *message = hasFirend ? @"好友以存在" : @"不能添加自己";
UIAlertController *alertVc = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertVc addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertVc animated:YES completion:nil];
}else {
//列表中不存在此好友則訂閱該好友
[[BQXMPPTool sharedXMPPTool].roster subscribePresenceToUser:jid];
}
//接下來是好友的驗證
#pragma mark 處理加好友回調,加好友
- (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence
{
//請求的用戶
NSString *presenceFromUser =[NSString stringWithFormat:@"%@", [[presence from] user]];
XMPPJID *jid = [XMPPJID jidWithString:presenceFromUser];
//接受該好友訂閱并訂閱該好友
[_roster acceptPresenceSubscriptionRequestFrom:jid andAddToRoster:YES];
}
//最后是好友的刪除,同樣根據好友的jid(XMPPJid)只需一個方法即可完成
[_roster removeUser:jid]; 4.聊天消息模塊
4.1聊天模塊的配置與激活
聊天模塊存放在xmpp框架中拓展文件夾XEP-0136下,默認未導入頭文件,需要導入其頭文件后才能使用
//創建聊天模塊存儲器
_messageStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
//創建聊天模塊
_message = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_messageStorage];
//激活聊天模塊
[_message activate:_xmppStream]; 4.2消息信息的獲取
消息的獲取也和上面花名冊的獲取大體相同,同樣是利用消息信息數據庫來獲取,下面是實現代碼
//獲取聊天消息的上下文
_msgContext = [BQXMPPTool sharedXMPPTool].messageStorage.mainThreadManagedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];
//排序
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:YES];
request.sortDescriptors = @[sort];
//將不滿足條件的過濾
NSPredicate *predic = [NSPredicate predicateWithFormat:@"bareJidStr = %@",_firendInfo.jidStr];
request.predicate = predic;
//獲取數據庫結果
_msgResultsCtl = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:_msgContext sectionNameKeyPath:nil cacheName:nil];
_msgResultsCtl.delegate = self;
NSError *error;
[_msgResultsCtl performFetch:&error];
if (error) {
BQLog(@"數據庫讀取出錯:%@",error.localizedDescription);
} 4.3消息的發送和讀取
在現有的xmpp消息體系中針對于iOS還不支持語音,圖片等資料傳輸,在此我們需要在其傳輸協議中添加節點來幫助我們判斷所傳輸內容格式。先讓我們來看看消息的傳輸格式,在其中我手動添加了MessageType來幫助我進行判斷此消息的類型(100:純文本,101:語音消息,102:圖片消息)。在后面即可根據消息類型來判斷如何加載消息(消息里可直接添加二進制數據文件)。此處本人做了資料(語音,圖片等)上傳處理,以減輕服務器壓力,這樣只傳輸url地址。當用戶讀取相對應消息后只需做好緩存即可。
下面直接上消息發送和獲取的代碼
//先來看看消息的發送,此處做了一個簡單的封裝
/** 根據傳入的消息類型和消息體來進行消息發送 */
- (void)sendMessageWithMsgType:(MessageType)msgType withBody:(NSString *)body {
//消息的發送需要用到核心類
XMPPStream *stream = [BQXMPPTool sharedXMPPTool].xmppStream;
//配置消息體,'chat'代表聊天消息
XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:_firendInfo.jid];
//增加一個消息類型節點
[message addAttributeWithName:@"MessageType" intValue:msgType];
//添加消息體
[message addBody:body];
//發送消息
[stream sendElement:message];
}
//消息的讀取
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
XMPPMessageArchiving_Message_CoreDataObject *msg = _msgResultsCtl.fetchedObjects[indexPath.row];
XMPPMessage *message = msg.message;
MessageType type = [message attributeIntValueForName:@"MessageType"];
UITableViewCell *cell;
//此時根據消息的類型再分別加載cell
switch (type) {
case MessageType_Text:
cell = [self tableView:tableView cellWithIdentifier:kIdentifiText];
break;
case MessageType_Sound:
cell = [self tableView:tableView cellWithIdentifier:kIdentifiSound];
break;
case MessageType_Image:
cell = [self tableView:tableView cellWithIdentifier:kIdentifiImage];
break;
default:
break;
}
return cell;
} 5.自動(斷線重練)鏈接模塊
在iOS當中若程序進入后臺那很可能就會導致客戶端與服務器的鏈接失效,此時便可導入自動連接模塊以方便重新鏈接,代碼較為簡單,變化不多說直接上代碼
//創建自動鏈接模塊
_reconnect = [[XMPPReconnect alloc] init];
//激活自動鏈接模塊
[_reconnect activate:_xmppStream]; 6.總結
看過上述代碼的同學可以發現,在xmpp當中不論什么模塊的使用無非就是3個步驟:
1.創建模塊
2.激活模塊
3.使用模塊
本篇文章中所羅列的都為關鍵代碼,更多的代碼并沒有展示出來,但通過上述代碼的學習,我相信各位都會對xmpp框架的使用方式和學習方式有一個大致方向。關于本篇所對應的完整代碼的鏈接如下 https://github.com/PurpleSweetPotatoes/XMPP_Learn_Demo ,本demo旨在學習xmpp框架的用法和思路,不完善處請見諒!如本文或代碼中有任何錯或不規范處請指出,謝謝!
來自: http://www.cnblogs.com/purple-sweet-pottoes/p/5090374.html