1. 程式人生 > >openfire+XMPP實現即時通訊

openfire+XMPP實現即時通訊

文章背景
大部分的APP都是盈利為目的而開發,與消費者互動必成重要的一個功能,有互動就有潛在客戶,而且大多數老闆都認為自己的APP沒有IM功能就會覺得不上檔次。目前市面上比較好點的即時通訊第三方有環信、融雲等。我不採用第三方的原因主要三點:1、第三方前期免費開放,有可能在圈使用者,不排除後期收費;2、其實第三方伺服器經常蛋機,總有正在維護升級,請耐心等待啥啥的,總感覺受之於人;3、第三方sdk伺服器都是好多人在共享,難免訊息延遲。

檔案目標
1、搭建XMPP伺服器
2、實現XMPP即時通訊

什麼是OpenFire
您可以使用它輕易的構建高效率的即時通訊伺服器.
Openfire安裝和使用都非常簡單,並利用Web進行管理。單臺伺服器可支援上萬併發使用者。
由於是採用開放的XMPP協議,您可以使用各種支援XMPP協議的IM客戶端軟體登陸服務.

1、什麼是XMPP
1-1、XMPP(可擴充套件訊息處理現場協議)是基於可擴充套件標記語言(XML)的協議,它用於即時訊息(IM)以及線上現場探測。
1-2、XMPP的前身是Jabber,一個開源形式組織產生的網路即時通訊協議

XMPP體系架構
XMPP的基本網路結構 ,xmpp定義了3個角色
Client
Server
Gateway
通訊能夠在這三者的任意兩個之間雙向發生。伺服器同時承擔了客戶端資訊記錄,連線管理和資訊的路由功能。閘道器承擔著與異構即時通訊系統的互聯互通,異構系統可以包括SMS(簡訊),MSN,ICQ等。基本的網路形式是單客戶端通過TCP/IP連線到單伺服器,然後在之上傳輸XML。
客戶端利用xmpp(基於TCP/IP)訪問server,傳輸的是XML
Client——–Server—-Client
TCP TCP TCP

XMPP server:其核心是一個XMPP路由器,完成基本元件間的資料包交換和路由。
功能:
1.會話管理器:負責客戶端會話認證,線上狀態,使用者聯絡表等
2.資料儲存器(XDB):連線資料庫系統,保持使用者資訊、通訊日誌等
3.聯結器管理器:管理與客戶端之間的連線
4.伺服器聯結器:管理xmpp伺服器之間的連線
5.傳輸器:建立xmpp伺服器與非xmpp伺服器通訊

XMPP工作原理圖

xm001

XMPP工作原理說明
所有從一個client到另一個client的jabber訊息和資料都要通過xmpp server。
1.client連線到server
2.server利用本地目錄系統的證書對其認證
3.client制定目標地址,讓server告知目標狀態
4.server查詢,連線並進行相互認證
5.client間進行互動

好了好了,原理就先說到這裡吧,想必實現才是我們最關心的的,下面就開始吧。

2、快速入門
step
2-1、準備資料庫

1

得到安裝檔案:
2

2-2-2、安裝openfire
2-2-2-1、雙擊可執行安裝檔案,點選下一步傻瓜式安裝即可。
安裝成功效果如下:
3

在安裝路徑我們可以看到生成的相關資料庫各種版本指令碼任你選擇。
4

2-2-2-2、執行指令碼得到openfire服務對應的資料庫
5

2-3、配置openfire

2-3-1、第一次啟動openfire會出現配置介面

6

next
7

next
8

next
9

next
10

next
11

next
12

next
13

看見沒,至此openfire可以正常工作啦!為XMPP客戶端提供了伺服器的能力。

2-4、XMPP實現

2-4-1、XMPPFramework下載
git下載址:https://github.com/dechengyang/XMPPFramework
官網地址:https://xmpp.org/
2-4-2、匯入XMPPFramework框架
xm1
2-4-3、新增相關動態庫
xm2
至此XMPPFramework框架已經匯入完畢。

2-4-4、XMPP工具封裝類
2-4-4-1、WCUserInfo類
.h檔案

import <Foundation/Foundation.h>
#import "Singleton.h"
static NSString *domain = @"ydc.local";
@interface WCUserInfo : NSObject

singleton_interface(WCUserInfo);

@property (nonatomic, copy) NSString *user;//使用者名稱
@property (nonatomic, copy) NSString *pwd;//密碼

/**
 *  登入的狀態 YES 登入過/NO 登出
 */
@property (nonatomic, assign) BOOL  loginStatus;


@property (nonatomic, copy) NSString *registerUser;//註冊的使用者名稱
@property (nonatomic, copy) NSString *registerPwd;//註冊的密碼
@property (nonatomic, copy) NSString *jid;

/**
 *  從沙盒裡獲取使用者資料
 */
-(void)loadUserInfoFromSanbox;

/**
 *  儲存使用者資料到沙盒

 */
-(void)saveUserInfoToSanbox;
@end

.m檔案


#import "WCUserInfo.h"

#define UserKey @"user"
#define LoginStatusKey @"LoginStatus"
#define PwdKey @"pwd"
@implementation WCUserInfo

singleton_implementation(WCUserInfo)

-(void)saveUserInfoToSanbox{

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:self.user forKey:UserKey];
    [defaults setBool:self.loginStatus forKey:LoginStatusKey];
    [defaults setObject:self.pwd forKey:PwdKey];
    [defaults synchronize];

}

-(void)loadUserInfoFromSanbox{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    self.user = [defaults objectForKey:UserKey];
    self.loginStatus = [defaults boolForKey:LoginStatusKey];
    self.pwd = [defaults objectForKey:PwdKey];
}


-(NSString *)jid{
    return [NSString stringWithFormat:@"%@@%@",self.user,domain];
}
@end

該類主要用來存放使用者資訊。

2-4-4-2、WCXMPPTool類
.h檔案

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

typedef enum {
    XMPPResultTypeLoginSuccess,//登入成功
    XMPPResultTypeLoginFailure,//登入失敗
    XMPPResultTypeNetErr,//網路不給力
    XMPPResultTypeRegisterSuccess,//註冊成功
    XMPPResultTypeRegisterFailure//註冊失敗
}XMPPResultType;

typedef void (^XMPPResultBlock)(XMPPResultType type);// XMPP請求結果的block

@interface WCXMPPTool : NSObject

singleton_interface(WCXMPPTool);

@property (nonatomic, strong,readonly)XMPPStream *xmppStream;
@property (nonatomic, strong,readonly)XMPPvCardTempModule *vCard;//電子名片
@property (nonatomic, strong,readonly)XMPPRosterCoreDataStorage *rosterStorage;//花名冊資料儲存
@property (nonatomic, strong,readonly)XMPPRoster *roster;//花名冊模組
@property (nonatomic, strong,readonly)XMPPMessageArchivingCoreDataStorage *msgStorage;//聊天的資料儲存
/**
 *  註冊標識 YES 註冊 / NO 登入
 */
@property (nonatomic, assign,getter=isRegisterOperation) BOOL  registerOperation;//註冊操作

/**
 *  使用者登出

 */
-(void)xmppUserlogout;
/**
 *  使用者登入
 */
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;


/**
 *  使用者註冊
 */
-(void)xmppUserRegister:(XMPPResultBlock)resultBlock;
@end

.m檔案


#import "WCXMPPTool.h"

/*
 * 在AppDelegate實現登入

 1. 初始化XMPPStream
 2. 連線到伺服器[傳一個JID]
 3. 連線到服務成功後,再發送密碼授權
 4. 授權成功後,傳送"線上" 訊息
 */
@interface WCXMPPTool ()<XMPPStreamDelegate>{

    XMPPResultBlock _resultBlock;

    XMPPReconnect *_reconnect;// 自動連線模組

    XMPPvCardCoreDataStorage *_vCardStorage;//電子名片的資料儲存

    XMPPvCardAvatarModule *_avatar;//頭像模組

    XMPPMessageArchiving *_msgArchiving;//聊天模組



}

// 1. 初始化XMPPStream
-(void)setupXMPPStream;


// 2.連線到伺服器
-(void)connectToHost;

// 3.連線到服務成功後,再發送密碼授權
-(void)sendPwdToHost;


// 4.授權成功後,傳送"線上" 訊息
-(void)sendOnlineToHost;
@end


@implementation WCXMPPTool


singleton_implementation(WCXMPPTool)



#pragma mark  -私有方法
#pragma mark 初始化XMPPStream
-(void)setupXMPPStream{

    _xmppStream = [[XMPPStream alloc] init];
#warning 每一個模組新增後都要啟用
    //新增自動連線模組
    _reconnect = [[XMPPReconnect alloc] init];
    [_reconnect activate:_xmppStream];

    //新增電子名片模組
    _vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
    _vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];

    //啟用
    [_vCard activate:_xmppStream];

    //新增頭像模組
    _avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];
    [_avatar activate:_xmppStream];


    // 新增花名冊模組【獲取好友列表】
    _rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
    _roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];
    [_roster activate:_xmppStream];

    // 新增聊天模組
    _msgStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
    _msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgStorage];
    [_msgArchiving activate:_xmppStream];

    // 設定代理
    [_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}

#pragma mark 釋放xmppStream相關的資源
-(void)teardownXmpp{

    // 移除代理
    [_xmppStream removeDelegate:self];

    // 停止模組
    [_reconnect deactivate];
    [_vCard deactivate];
    [_avatar deactivate];
    [_roster deactivate];
    [_msgArchiving deactivate];

    // 斷開連線
    [_xmppStream disconnect];

    // 清空資源
    _reconnect = nil;
    _vCard = nil;
    _vCardStorage = nil;
    _avatar = nil;
    _roster = nil;
    _rosterStorage = nil;
    _msgArchiving = nil;
    _msgStorage = nil;
    _xmppStream = nil;

}
#pragma mark 連線到伺服器
-(void)connectToHost{
    MyLog(@"開始連線到伺服器");
    if (!_xmppStream) {
        [self setupXMPPStream];
    }


    // 設定登入使用者JID
    //resource 標識使用者登入的客戶端 iphone android

    // 從單例獲取使用者名稱
    NSString *user = nil;
    if (self.isRegisterOperation) {
        user = [WCUserInfo sharedWCUserInfo].registerUser;
    }else{
        user = [WCUserInfo sharedWCUserInfo].user;
    }

    XMPPJID *myJID = [XMPPJID jidWithUser:user domain:@"ydc.local" resource:@"iphone" ];
    _xmppStream.myJID = myJID;

    // 設定伺服器域名
    //_xmppStream.hostName = @"120.27.137.134";//不僅可以是域名,還可是IP地址
    //_xmppStream.hostName = @"ysindustrial.com";//不僅可以是域名,還可是IP地址
    _xmppStream.hostName = @"192.168.1.100";//不僅可以是域名,還可是IP地址
    //_xmppStream.hostName = @"192.168.0.100";

    // 設定埠 如果伺服器埠是5222,可以省略
    _xmppStream.hostPort = 5222;

    // 連線
    NSError *err = nil;
    if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){
        MyLog(@"%@",err);
    }

}


#pragma mark 連線到服務成功後,再發送密碼授權
-(void)sendPwdToHost{
    MyLog(@"再發送密碼授權");
    NSError *err = nil;

    // 從單例裡獲取密碼
    NSString *pwd = [WCUserInfo sharedWCUserInfo].pwd;

    [_xmppStream authenticateWithPassword:pwd error:&err];

    if (err) {
        MyLog(@"%@",err);
    }
}

#pragma mark  授權成功後,傳送"線上" 訊息
-(void)sendOnlineToHost{

    MyLog(@"傳送 線上 訊息");
    XMPPPresence *presence = [XMPPPresence presence];
    MyLog(@"%@",presence);

    [_xmppStream sendElement:presence];


}
#pragma mark -XMPPStream的代理
#pragma mark 與主機連線成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    MyLog(@"與主機連線成功");

    if (self.isRegisterOperation) {//註冊操作,傳送註冊的密碼
        NSString *pwd = [WCUserInfo sharedWCUserInfo].registerPwd;
        [_xmppStream registerWithPassword:pwd error:nil];
    }else{//登入操作
        // 主機連線成功後,傳送密碼進行授權
        [self sendPwdToHost];
    }

}
#pragma mark  與主機斷開連線
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    // 如果有錯誤,代表連線失敗

    // 如果沒有錯誤,表示正常的斷開連線(人為斷開連線)


    if(error && _resultBlock){
        _resultBlock(XMPPResultTypeNetErr);
    }
    MyLog(@"與主機斷開連線 %@",error);

}


#pragma mark 授權成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
    MyLog(@"授權成功");

    [self sendOnlineToHost];

    // 回撥控制器登入成功
    if(_resultBlock){
        _resultBlock(XMPPResultTypeLoginSuccess);
    }


}


#pragma mark 授權失敗
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
    MyLog(@"授權失敗 %@",error);

    // 判斷block有無值,再回調給登入控制器
    if (_resultBlock) {
        _resultBlock(XMPPResultTypeLoginFailure);
    }
}

#pragma mark 註冊成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender{
    MyLog(@"註冊成功");
    if(_resultBlock){
        _resultBlock(XMPPResultTypeRegisterSuccess);
    }

}

#pragma mark 註冊失敗
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{

    MyLog(@"註冊失敗 %@",error);
    if(_resultBlock){
        _resultBlock(XMPPResultTypeRegisterFailure);
    }

}

#pragma mark -公共方法
-(void)xmppUserlogout{
    // 1." 傳送 "離線" 訊息"
    XMPPPresence *offline = [XMPPPresence presenceWithType:@"unavailable"];
    [_xmppStream sendElement:offline];

    // 2. 與伺服器斷開連線
    [_xmppStream disconnect];

    // 3. 回到登入介面
//    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:nil];
//    
//    self.window.rootViewController = storyboard.instantiateInitialViewController;

    //ydc 2016080808 報錯註釋
    //[UIStoryboard showInitialVCWithName:@"Login"];


    //4.更新使用者的登入狀態
    [WCUserInfo sharedWCUserInfo].loginStatus = NO;
    [[WCUserInfo sharedWCUserInfo] saveUserInfoToSanbox];

}

-(void)xmppUserLogin:(XMPPResultBlock)resultBlock{

    // 先把block存起來
    _resultBlock = resultBlock;

    //    Domain=XMPPStreamErrorDomain Code=1 "Attempting to connect while already connected or connecting." UserInfo=0x7fd86bf06700 {NSLocalizedDescription=Attempting to connect while already connected or connecting.}
    // 如果以前連線過服務,要斷開
    [_xmppStream disconnect];

    // 連線主機 成功後傳送登入密碼
    [self connectToHost];
}


-(void)xmppUserRegister:(XMPPResultBlock)resultBlock{
    // 先把block存起來
    _resultBlock = resultBlock;

    // 如果以前連線過服務,要斷開
    [_xmppStream disconnect];

    // 連線主機 成功後傳送註冊密碼
    [self connectToHost];
}


-(void)dealloc{
    [self teardownXmpp];
}
@end

把XMPP核心功能都封裝在該類中,方便統一呼叫。

2-4-4-3、Singleton類

// .h
#define singleton_interface(class) + (instancetype)shared##class;

// .m
#define singleton_implementation(class) \
static class *_instance; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [super allocWithZone:zone]; \
    }); \
\
    return _instance; \
} \
\
+ (instancetype)shared##class \
{ \
    if (_instance == nil) { \
        _instance = [[class alloc] init]; \
    } \
\
    return _instance; \
}

2-4-5、使用者註冊
2-4-5-1、註冊流程
1.初始化XMPPStream
2.連線到伺服器[傳一個JID]
3.連線到服務成功後,再發送密碼授權

2-4-5-2、註冊控制器中的程式碼如下:


//ydc 20160809 openfire使用者註冊
-(void)XMPPRegisterUser:(NSString *)username :(NSString *)pwd{

    // 1.把使用者註冊的資料儲存單例
    WCUserInfo *userInfo = [WCUserInfo sharedWCUserInfo];
    userInfo.registerUser =username;
    userInfo.registerPwd = openfirePassword;

    // 2.呼叫WCXMPPTool的xmppUserRegister
    [WCXMPPTool sharedWCXMPPTool].registerOperation = YES;

    // 提示
    [MBProgressHUD showMessage:@"正在註冊中....." toView:self.view];

    __weak typeof(self) selfVc = self;
    [[WCXMPPTool sharedWCXMPPTool] xmppUserRegister:^(XMPPResultType type) {

        [selfVc handleResultType:type];
    }];
}

/**
 *  處理註冊的結果
 */
-(void)handleResultType:(XMPPResultType)type{

    dispatch_async(dispatch_get_main_queue(), ^{
        [MBProgressHUD hideHUDForView:self.view];
        switch (type) {
            case XMPPResultTypeNetErr:
                [MBProgressHUD showError:@"網路不穩定" toView:self.view];
                break;
            case XMPPResultTypeRegisterSuccess:
                //[MBProgressHUD showError:@"註冊成功" toView:self.view];
                [self showSVString:@"註冊成功"];

            {
                __weak RegisterViewController *weakSelf = self;
                dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
                dispatch_after(delayTime, dispatch_get_main_queue(), ^{
                    [weakSelf delayMethod];

                });
            }
                                               // 回到上個控制器
                /**[self dismissViewControllerAnimated:YES completion:nil];

                if ([self.delegate respondsToSelector:@selector(regisgerViewControllerDidFinishRegister)]) {
                    [self.delegate regisgerViewControllerDidFinishRegister];
                }**/
                break;

            case XMPPResultTypeRegisterFailure:
                [MBProgressHUD showError:@"註冊失敗,使用者名稱重複" toView:self.view];
                break;
            default:
                break;
        }
    });


}

我們進入WCXMPPTool的xmppUserRegister方法如下:

-(void)xmppUserRegister:(XMPPResultBlock)resultBlock{
    // 先把block存起來
    _resultBlock = resultBlock;

    // 如果以前連線過服務,要斷開
    [_xmppStream disconnect];

    // 連線主機 成功後傳送註冊密碼
    [self connectToHost];
}

繼續進入connectToHost方法


#pragma mark 連線到伺服器
-(void)connectToHost{
    MyLog(@"開始連線到伺服器");
    if (!_xmppStream) {
        [self setupXMPPStream];
    }


    // 設定登入使用者JID
    //resource 標識使用者登入的客戶端 iphone android

    // 從單例獲取使用者名稱
    NSString *user = nil;
    if (self.isRegisterOperation) {
        user = [WCUserInfo sharedWCUserInfo].registerUser;
    }else{
        user = [WCUserInfo sharedWCUserInfo].user;
    }

    XMPPJID *myJID = [XMPPJID jidWithUser:user domain:@"ydc.local" resource:@"iphone" ];
    _xmppStream.myJID = myJID;

    // 設定伺服器域名
    //_xmppStream.hostName = @"120.27.137.134";//不僅可以是域名,還可是IP地址
    //_xmppStream.hostName = @"ysindustrial.com";//不僅可以是域名,還可是IP地址
    _xmppStream.hostName = @"192.168.1.100";//不僅可以是域名,還可是IP地址
    //_xmppStream.hostName = @"192.168.0.100";

    // 設定埠 如果伺服器埠是5222,可以省略
    _xmppStream.hostPort = 5222;

    // 連線
    NSError *err = nil;
    if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){
        MyLog(@"%@",err);
    }

}

當執行完上一步之後,XMPP框架會自動回撥到下面的其中一個代理方法中

#pragma mark -XMPPStream的代理
#pragma mark 與主機連線成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    MyLog(@"與主機連線成功");

    if (self.isRegisterOperation) {//註冊操作,傳送註冊的密碼
        NSString *pwd = [WCUserInfo sharedWCUserInfo].registerPwd;
        [_xmppStream registerWithPassword:pwd error:nil];
    }else{//登入操作
        // 主機連線成功後,傳送密碼進行授權
        [self sendPwdToHost];
    }

}
#pragma mark  與主機斷開連線
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
    // 如果有錯誤,代表連線失敗

    // 如果沒有錯誤,表示正常的斷開連線(人為斷開連線)


    if(error && _resultBlock){
        _resultBlock(XMPPResultTypeNetErr);
    }
    MyLog(@"與主機斷開連線 %@",error);

}

2-4-5-3、效果如下:
xm3

mx4

xm5

xm6

2-4-6、使用者登入
2-4-6-1、登入流程
1.初始化XMPPStream
2.連線到伺服器[傳一個JID]
3.連線到服務成功後,再發送密碼授權
4.授權成功後,傳送”線上” 訊息

2-4-6-1、登入控制器中的登入程式碼如下:

//ydc 20160809 openfire使用者登入
- (void)XMPPlogin:(NSString *)username: (NSString*)pwd{
    // 儲存資料到單例
    WCUserInfo *userInfo = [WCUserInfo sharedWCUserInfo];
    userInfo.user = username;
    userInfo.pwd = openfirePassword;
    //隱藏鍵盤
    [self.view endEditing:YES];
    // 登入之前給個提示
    [MBProgressHUD showMessage:@"正在登入中..." toView:self.view];
    [WCXMPPTool sharedWCXMPPTool].registerOperation = NO;
    __weak typeof(self) selfVc = self;
    [[WCXMPPTool sharedWCXMPPTool] xmppUserLogin:^(XMPPResultType type) {
        [selfVc handleResultType:type];
    }];
}

-(void)handleResultType:(XMPPResultType)type{
    // 主執行緒重新整理UI
    dispatch_async(dispatch_get_main_queue(), ^{
        [MBProgressHUD hideHUDForView:self.view
         ];
        switch (type) {
            case XMPPResultTypeLoginSuccess:
                NSLog(@"登入成功");
                [self enterMainPage];
                [self tapAction];
                break;
            case XMPPResultTypeLoginFailure:
                NSLog(@"登入失敗");
                [MBProgressHUD showError:@"使用者名稱或者密碼不正確" toView:self.view];
                break;
            case XMPPResultTypeNetErr:
                [MBProgressHUD showError:@"網路不給力" toView:self.view];
            default:
                break;
        }
    });

}

登入只比註冊多一個”授權成功後,傳送”線上” 訊息”步驟,程式碼如下:

#pragma mark  授權成功後,傳送"線上" 訊息
-(void)sendOnlineToHost{

    MyLog(@"傳送 線上 訊息");
    XMPPPresence *presence = [XMPPPresence presence];
    MyLog(@"%@",presence);

    [_xmppStream sendElement:presence];

}

2-4-6-2、效果如下:

xm8

xm001

xm9

看見沒,我們已經成功登陸到了openfire伺服器啦!

2-4-7、新增好友
2-4-7-1、新增好友控制器中的新增好友程式碼如下:

-(BOOL)submitAction{
    // 新增好友
    // 1.獲取好友賬號
    NSString *user = _phoneNumTextfield.text;
    MyLog(@"%@",user);
    // 判斷這個賬號是否為手機號碼
    if([CustomHelper checkTelNumber:_phoneNumTextfield.text]){

    }else{
        //提示
        [self showAlert:@"請輸入正確的手機號碼"];
        return YES;
    }

    //判斷是否新增自己
    if([user isEqualToString:[WCUserInfo sharedWCUserInfo].user]){

        [self showAlert:@"不能新增自己為好友"];
        return YES;
    }
    NSString *jidStr = [NSString stringWithFormat:@"%@@%@",user,domain];
    XMPPJID *friendJid = [XMPPJID jidWithString:jidStr];
    //判斷好友是否已經存在
    if([[WCXMPPTool sharedWCXMPPTool].rosterStorage userExistsWithJID:friendJid xmppStream:[WCXMPPTool sharedWCXMPPTool].xmppStream]){
        [self showAlert:@"當前好友已經存在"];
        return YES;
    }

    // 2.傳送好友新增的請求
    // 新增好友,xmpp有個叫訂閱
    [[WCXMPPTool sharedWCXMPPTool].roster subscribePresenceToUser:friendJid];

    return YES;
}

2-4-7-2、效果 如下:
xm101

xm103

xm1007

xm1008
看見沒,我們已經把好友關係成功維護到了伺服器端資料庫中的好友關係表中

2-4-8、載入好友列表
2-4-8-1、兩個核心類

#import "XMPPRoster.h"
#import "XMPPCoreDataStorage.h"

2-4-8-2、實現資料變化監聽代理NSFetchedResultsControllerDelegate

#pragma mark 當資料的內容發生改變後,會呼叫 這個方法
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
    MyLog(@"資料發生改變");
    [self loadFriends2];
    //重新整理表格
    [self.cityTableView reloadData];
}

2-4-8-3、獲取好友列表核心程式碼如下:

-(void)loadFriends2{
    //使用CoreData獲取資料
    // 1.上下文【關聯到資料庫XMPPRoster.sqlite】
    NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].rosterStorage.mainThreadManagedObjectContext;


    // 2.FetchRequest【查哪張表】
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPUserCoreDataStorageObject"];

    // 3.設定過濾和排序
    // 過濾當前登入使用者的好友
    NSString *jid = [WCUserInfo sharedWCUserInfo].jid;
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@",jid];
    request.predicate = pre;

    //排序
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES];
    request.sortDescriptors = @[sort];

    // 4.執行請求獲取資料
    _resultsContrl = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

    _resultsContrl.delegate = self;

    NSError *err = nil;
    [_resultsContrl performFetch:&err];
    if (err) {
        MyLog(@"%@",err);
    }

    [self.view addSubview:self.cityTableView];

}

2-4-8-4、效果如下:
xm201

2-4-9、傳送訊息
2-4-9-1、傳送訊息程式碼如下:

#pragma mark 傳送聊天訊息
-(void)sendMsgWithText:(NSString *)text{

    XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];
    // 設定內容
    [msg addBody:text];
    NSLog(@"%@",msg);
    [[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
}

2-4-9-2、效果如下:

xm14

xm15

看見沒,我們把訊息通過XMPP框架,再通過openfire服務成功傳送到openfire服務對應的資料庫中。openfire服務和資料有可能是在你本機上,也有可能部署在遙遠的阿里雲等伺服器中。如果你高興,可以使用Spark(現成的openfire客戶端)與之通訊,完全no problem。
Spark下載地址:http://www.igniterealtime.org/downloads/index.jsp

2-4-10、載入聊天資料
2-4-10-1、實現資料變化監聽代理NSFetchedResultsControllerDelegate

#pragma mark ResultController的代理
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{
    // 重新整理資料
    [_msgTableView reloadData];
    [self scrollToTableBottom];
}

2-4-10-2、載入聊天資料核心程式碼如下:

#pragma mark 載入XMPPMessageArchiving資料庫的資料顯示在表格
-(void)loadMsgs{

    // 上下文
    NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].msgStorage.mainThreadManagedObjectContext;
    // 請求物件
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];
    // 過濾、排序
    // 1.當前登入使用者的JID的訊息
    // 2.好友的Jid的訊息
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@ AND bareJidStr = %@",[WCUserInfo sharedWCUserInfo].jid,self.friendJid.bare];
    NSLog(@"%@",pre);
    request.predicate = pre;

    // 時間升序
    NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:YES];
    request.sortDescriptors = @[timeSort];

    // 查詢
    _resultsContr = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

    NSError *err = nil;
    // 代理
    _resultsContr.delegate = self;

    [_resultsContr performFetch:&err];

    NSLog(@"%@",_resultsContr.fetchedObjects);
    if (err) {
        MyLog(@"%@",err);
    }

    // 重新整理資料
    [_msgTableView reloadData];
    [self scrollToTableBottom];
}

2-4-10-3、資料載入方式
xmpp框架其實考慮到效能問題,框架不會實時去openfire伺服器讀取資料,而是每一次使用者登入成功之後資料同步到本地(每次同步本地沒有的資料),然後介面層是去本地(SQLite)讀取。

你可以在沙盒中看到幾個資料庫:
xm1500

然後使用以下工具開啟資料庫檢視:
xm1600

如果對你有幫助,賞我1元奶粉錢吧,多謝!
微信:

009

支付寶

008