iOS------XMPP實現一個簡單的聊天頁面

XMPP協議的優點:

開放-------XMPP協議是自由,開放,公開的,并且易于了解.而且在客戶端,服務器,組件,源碼庫等方面,都已經各自有多種實現.

標準-------互聯網工程工作小組(IETF)已經將Jabber的核心XML流協議以XMPP之名,正式列為認可的實時通信及Presence技術。而XMPP的技術規格已被定義在RFC 3920及RFC 3921。任何IM供應商在遵循XMPP協議下,都可與Google Talk實現連接。

證實可用-------第一個Jabber(現在XMPP)技術是Jeremie Miller在1998年開發的,現在已經相當穩定;數以百計的開發者為XMPP技術而努力。今日的互聯網上有數以萬計的XMPP服務器運作著,并有數以百萬計的人們使用XMPP實時傳訊軟件。
分布式-------XMPP網絡的架構和電子郵件十分相像;XMPP核心協議通信方式是先創建一個stream,XMPP以TCP傳遞XML數據流,沒有中央主服務器。任何人都可以運行自己的XMPP服務器,使個人及組織能夠掌控他們的實時傳訊體驗。

安全-------任何XMPP協議的服務器可以獨立于公眾XMPP網絡(例如在企業內部網絡中),而使用SASL及TLS等技術的可靠安全性,已自帶于核心XMPP技術規格中。

可擴展-------XML命名空間的威力可使任何人在核心協議的基礎上建造客制化的功能;為了維持通透性,常見的擴展由XMPPStandards Foundation。

彈性佳-------XMPP除了可用在實時通信的應用程序,還能用在網絡管理、內容供稿、協同工具、文件共享、游戲、遠程系統監控等。
多樣性—用XMPP協議來建造及布署實時應用程序及服務的公司及開放源代碼計劃分布在各種領域;用XMPP技術開發軟件,資源及支持的來源是多樣的,使得使你不會陷于被“綁架”的困境。

XMPP的缺點:

數據負載太重:隨著通常超過70%的XMPP協議的服務器的數據流量的存在和近60%的被重復轉發,XMPP協議目前擁有一個大型架空中存在的數據提供給多個收件人。新的議定書正在研究,以減輕這一問題。

沒有二進制數據:XMPP協議的方式被編碼為一個單一的長的XML文件,因此無法提供修改二進制數據。因此, 文件傳輸協議一樣使用外部的HTTP。如果不可避免,XMPP協議還提供了帶編碼的文件傳輸的所有數據使用的Base64。至于其他二進制數據加密會話(encrypted conversations)或圖形圖標(graphic icons)以嵌入式使用相同的方法。

XMPP實現簡單聊天

想要實現簡單的聊天首先要搭建一個服務器,聊天實現的原理就是,一個客戶端通過XMPP協議把信息傳給服務器,服務器在發消息發給里一個客戶端.
下面說一下如何搭建服務器,可以調整我寫的博客:
http://www.lxweimin.com/p/d47a2fa85009

D01FFF3A-2A77-41E3-A216-7327C509474D.png

下面是需要導入的文件:


屏幕快照 2016-03-04 下午8.42.31.png
BDC41DD0-28C8-49F3-A200-6C028AB323BB.png

簡單的創建一個好友列表和聊天界面(記得給cell重用標識符)


80DDF77F-3DE0-4303-925D-563F00E7192B.png

聊天界面的cell 的樣式記得更換


C84984B0-F726-4F88-932D-0E0D133BF449.png

簡單的創建一個登陸和注冊界面,主入口一樣要加上


2F40FE19-6D6C-4017-BD6C-0D192EC888FA.png

至此基本的準備工作已經做好了,記得把登陸注冊界面的TextField拖成屬性.

下面的代碼是寫在Appdelegate里面的:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// 拿到storyBoard對象
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"LoginAndRegister" bundle:nil];
// 拿到navigation控制器
UINavigationController *naVC = [storyBoard instantiateInitialViewController];

[self.window makeKeyAndVisible];

[self.window.rootViewController presentViewController:naVC animated:YES completion:nil];

return YES;
 }  

首先我們需要創建一個XMPPManager.h文件,繼承自NSObject

#import <Foundation/Foundation.h>
#import "XMPPFramework.h"

@interface XMPPManager : NSObject

@property (nonatomic ,strong) XMPPStream *stream;
@property (nonatomic ,strong) XMPPRoster *roster;
// XMPP聊天消息本地化處理對象
@property (nonatomic ,strong) XMPPMessageArchiving *messageArchiving;
@property (nonatomic ,strong) NSManagedObjectContext *messageContext;
// 單例
+ (XMPPManager *)defaultManager;
// 登陸:用于傳值(用戶名和密碼)
- (void)loginWithUserName:(NSString *)name andPassWord:(NSString *)passWord;
// 注冊:用于傳值(用戶名和密碼)
- (void)registerWithUserName:(NSString *)name andPassWord:(NSString *)passWored;

@end

#import "XMPPManager.h"

typedef enum :NSUInteger {
    DoLogin,
    DoRegister,
}ConnectType;


@interface XMPPManager () <XMPPStreamDelegate, XMPPRosterDelegate>

@property (nonatomic ,copy) NSString *passWord;
@property (nonatomic ,copy) NSString *registerWord;
@property (nonatomic ,assign) ConnectType connectType;
@end

@implementation XMPPManager

+ (XMPPManager *)defaultManager
{
    static XMPPManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[XMPPManager alloc] init];
    });
    return manager;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.stream = [[XMPPStream alloc] init];
        self.stream.hostName = kHostName;
        self.stream.hostPort = kHostPort;
        // 設置stream的代理
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        /** 下面的操作其實是在對Roster對象進行初始化*/
        
        // 系統寫好的xmpp存儲對象
        XMPPRosterCoreDataStorage *rosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
        self.roster = [[XMPPRoster alloc] initWithRosterStorage:rosterStorage dispatchQueue:dispatch_get_global_queue(0, 0)];
        // 激活roster
        [self.roster activate:self.stream];
        // 給roster對象指定代理
        [self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        // 初始化聊天記錄管理對象
        XMPPMessageArchivingCoreDataStorage *archiving = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        self.messageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:archiving dispatchQueue:dispatch_get_main_queue()];
        // 激活管理對象
        [self.messageArchiving activate:self.stream];
        // 給管理對象添加代理
        [self.messageArchiving addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        self.messageContext = archiving.mainThreadManagedObjectContext;
        
    }
    return self;
}

// 與服務器建立鏈接
- (void)connectToSercerWithUser:(NSString *)user
{
    if ([self.stream isConnected]) {
        [self disconnectWithSercer];
    }
    // jid 就是jabberID, 是基于Jabber協議的由用戶名生成的唯一ID
    self.stream.myJID = [XMPPJID jidWithUser:user domain:kDomin resource:kResource];
    NSError *error = nil;
    [self.stream connectWithTimeout:30.0 error:&error];
    if (error != nil) {
        NSLog(@"出現問題");
    }
}
// 與服務器斷開鏈接
- (void)disconnectWithSercer
{
    [self.stream disconnect];
}
// 登陸
- (void)loginWithUserName:(NSString *)name andPassWord:(NSString *)passWord
{
    self.connectType = DoLogin;
    self.passWord = passWord;
    [self connectToSercerWithUser:name];
}

// 與服務器建立鏈接成功
- (void)xmppStreamDidConnect:(XMPPStream *)sender
{
    switch (self.connectType) {
        case DoLogin:
        {
            NSLog(@"chenggong");
            // 與服務器進行登錄認證
            NSError *error = nil;
            [self.stream authenticateWithPassword:self.passWord error:&error];
            if (error != nil) {
                NSLog(@"出現問題");
            }
            break;
        }
        case DoRegister:
        {
            // 與服務器進行登錄認證
            NSError *error1 = nil;
            [self.stream registerWithPassword:self.registerWord error:&error1];
            if (error1 != nil) {
                NSLog(@"出現問題");
            }
            break;
        }
        default:
            break;
    }
    
}
// 拋出異常(手動讓程序崩潰)
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender
{
    NSLog(@"shibai");
    @throw [NSException exceptionWithName:@"CQ_Error" reason:@"與服務器建立鏈接失敗, 請查看代碼" userInfo:nil];
}

// 注冊
- (void)registerWithUserName:(NSString *)name andPassWord:(NSString *)passWored
{
    self.connectType = DoRegister;
    self.registerWord = passWored;
    [self connectToSercerWithUser:name];
}

接著要創建一個LoginViewController并與stroyboard關聯

#import "LoginViewController.h"
#import "XMPPManager.h"

@interface LoginViewController () <XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userName;
@property (weak, nonatomic) IBOutlet UITextField *passWord;

@end

@implementation LoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (IBAction)loginClick:(UIButton *)sender {
    NSString *userName = self.userName.text;
    NSString *userPWD = self.passWord.text;
    
    // 用用戶名和密碼進行登陸
    [[XMPPManager defaultManager] loginWithUserName:userName andPassWord:userPWD];
    
}
// 認證成功的時候調用的
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
// 注意: 這段代碼的主要作用是讓你登陸的賬號的狀態改為在線狀態,如果沒有添加,別人給你發的消息服務器默認為離線狀態,是不會給你發送的
    XMPPPresence *presen = [XMPPPresence presenceWithType:@"available"];
    [[XMPPManager defaultManager].stream sendElement:presen];
    NSLog(@"111111");
    [self dismissViewControllerAnimated:YES completion:nil];
}

// 認證失敗
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error
{
    NSLog(@"認證失敗,請重新輸入");
}

下面是注冊界面,創建一個RegisterViewController并與storyboard關聯:

#import "RegisterViewController.h"
#import "XMPPManager.h"

@interface RegisterViewController () <XMPPStreamDelegate>
@property (weak, nonatomic) IBOutlet UITextField *userNameTF;
@property (weak, nonatomic) IBOutlet UITextField *passWordTF;

@end

@implementation RegisterViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}
- (IBAction)registerClick:(UIButton *)sender {
    NSString *userName = self.userNameTF.text;
    NSString *passWord = self.passWordTF.text;
    
    [[XMPPManager defaultManager] registerWithUserName:userName andPassWord:passWord];
    NSLog(@"1");
}

// 當注冊成功的時候調用
- (void)xmppStreamDidRegister:(XMPPStream *)sender
{
    [self.navigationController popToRootViewControllerAnimated:YES];
}
// 注冊失敗的時候調用
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{
    NSLog(@"注冊失敗");
}

接下來就是好友列表界面了:

#import "MainTableViewController.h"
#import "ChatTableViewController.h"
#import "XMPPManager.h"

@interface MainTableViewController () <XMPPRosterDelegate>
// 用來存儲所有的好友信息
@property (nonatomic ,strong) NSMutableArray *allData;
// 通過用戶名生成的標識
@property (nonatomic ,strong) XMPPJID *chatToJid;
@end

@implementation MainTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.allData = [NSMutableArray array];
    [[XMPPManager defaultManager].roster addDelegate:self delegateQueue:dispatch_get_main_queue()];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.allData.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"mainCell" forIndexPath:indexPath];
    XMPPJID *jid = self.allData[indexPath.row];
    cell.textLabel.text = [NSString stringWithFormat:@"%@@%@",jid.user,jid.domain];
    
    return cell;
}

#pragma mark rosterDalegate
// 正在獲取好友列表
- (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(DDXMLElement *)item
{
    NSLog(@"****************");
    // 將每一個好友信息存儲下來
    NSLog(@"%@",item);
    NSString *jidString = [[item attributeForName:@"jid"] stringValue];
    XMPPJID *jid = [XMPPJID jidWithString:jidString];
    // 存儲到數組
    [self.allData addObject:jid];
    // 更新到UI界面
    NSIndexPath *path = [NSIndexPath indexPathForRow:self.allData.count - 1 inSection:0];
    [self.tableView insertRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationRight];
}
// 開始獲取好友列表的時候
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender
{
    NSLog(@"---------------****************");
}
// 結束獲取好友列表的時候
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender
{
    NSLog(@"*******----------------********");
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    UITableViewCell *cell = (UITableViewCell *)sender;
    // 拿到下一步要跳轉的Controller對象
    ChatTableViewController *cVC = segue.destinationViewController;
    // 判斷選中的cell在當前的table中的位置
    NSIndexPath *path = [self.tableView indexPathForCell:cell];
    cVC.chatToJid = self.allData[path.row];
}

聊天界面:

#import <UIKit/UIKit.h>
#import "XMPPManager.h"

@interface ChatTableViewController : UITableViewController
@property (nonatomic ,strong) XMPPJID *chatToJid;
@end
#import "ChatTableViewController.h"
#import "XMPPManager.h"

@interface ChatTableViewController () <XMPPStreamDelegate>
@property (nonatomic ,strong) NSMutableArray *messageArray;

@property (nonatomic ,strong) NSString *sendMessage;
@property (nonatomic ,strong) NSString *message;

@end

@implementation ChatTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.messageArray = [NSMutableArray array];
    [[XMPPManager defaultManager].stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
    // 更新聊天記錄信息
    [self reloadMessage];
}
// 展現聊天記錄
- (void)reloadMessage
{
    NSManagedObjectContext *context = [XMPPManager defaultManager].messageContext;
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    
    /** 獲取所有的聊天記錄 */
    // 這里面要填的是XMPPARCHiver的coreData實例類型
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"XMPPMessageArchiving_Message_CoreDataObject" inManagedObjectContext:context];
    [fetchRequest setEntity:entity];
    // Specify criteria for filtering which objects to fetch
    // 對取到的數據進行過濾,傳入過濾條件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"streamBareJidStr==%@ AND bareJidStr == %@", [XMPPManager defaultManager].stream.myJID.bare,self.chatToJid.bare];
    [fetchRequest setPredicate:predicate];
    // Specify how the fetched objects should be sorted
    // 設置排序的關鍵字
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timestamp" ascending:YES];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]];
    
    NSError *error = nil;
    NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
    if (fetchedObjects == nil) {
        // 完成之后
        NSLog(@"I'll be back!");
    }
    // 清空消息數組里的所有數據
    [self.messageArray removeAllObjects];
    // 將新的聊天記錄添加到數組中
    [self.messageArray addObjectsFromArray:fetchedObjects];
    [self.tableView reloadData];

    if (self.messageArray.count) {
        // 判斷當前數組的元素個數,代碼保護
        // 滑動到UITableView的最底部,保證用戶看到的是最新的消息
        NSIndexPath *indexPath
        = [NSIndexPath indexPathForRow:self.messageArray.count - 1 inSection:0];
        [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
    }
}
- (IBAction)sendAction:(UIBarButtonItem *)sender {
    // 創建消息實體
    XMPPMessage *message = [XMPPMessage messageWithType:@"chat" to:self.chatToJid];
    
    [message addBody:@"Hello Word!"];
    // 發送消息
    [[XMPPManager defaultManager].stream sendElement:message];
    [self reloadMessage];
}

//  成功發送消息
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
    NSLog(@"%s__%d__|message = %@", __FUNCTION__, __LINE__,message);
    [self reloadMessage];
}

//  收到他人發送的消息
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message
{
    NSLog(@"%s__%d__|message = %@", __FUNCTION__, __LINE__,message);
    [self reloadMessage];
}

- (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error
{
    NSLog(@"發送失敗------%@",error);
}


#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.messageArray.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"chatCell" forIndexPath:indexPath];
    // 取到對應的信息
    XMPPMessageArchiving_Message_CoreDataObject *message = self.messageArray[indexPath.row];
    
    if (message.isOutgoing == YES) {
        cell.detailTextLabel.text = message.body;
        cell.textLabel.text = @"";
    } else {
        cell.textLabel.text = message.body;
        cell.detailTextLabel.text = @"";
    }
    
    return cell;
}
QQ20160304-1.png
QQ20160304-0.png

消息即使到達

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容