XMPP通信

IOS開發,OC和Swift語言對照,XMPP通信的實現:


一、導入XMPP框架

  1. 下載 XMPPFramework 框架 

    GitHub: XMPPFramework

  2. 導入依賴框架 

    • CocoaLumberjack : 日誌框架 

    • CocoaAsyncSocket : 底層網絡框架
      需要添加 CFNetwork & Security 框架依賴(XCode 6+ 無需導入) 

    • KissXML : XML解析框架
      需要添加libxml2.dylib框架依賴
      需要指定如下編譯選項:
      Other Linker Flags = -lxml2
      Header Search Paths = /usr/include/libxml2 

    • libidn

  3. 導入一下文件夾 

    • Authentication
    • Categories
    • Core
    • Utilities
    • 添加依賴:libresolv.dylib
  4. 導入XMPP擴展框架 

    • Extensions 文件夾 
    • 導入 Sample_XMPPFramework.h 並更名爲:XMPPFramework.h
    • 添加PCH文件: 
      • Add New File -> Other -> PCH文件
      • 添加 #import<UIKit/UIKit.h> 到PCH文件中 
      • 設置編譯選項,Build Settings -> Precompile prefix Header 選擇Yes
      • 設置編譯選項,Build Settings -> Prefix Header 設置PCH文件名:“項目名/“PCH文件名”
  5. 導入Swift-OC橋接頭文件(Swift)
    • 添加文件
      • Add New File -> Source -> HeaderFile
      • 添加 #import<XMPP.h>到橋接頭文件中
      • 設置編譯選項,Build Settings -> Objective-C Bridging Header 設置橋接頭文件名 "項目名/橋接頭文件名"

二、登錄 & 註銷

實現用戶登錄的步驟如下:

1. 實例化XMPPStream並設置代理,同時添加代理到工作隊列      

2. 使用JID連接至服務器,默認端口爲5222,JID字符串中需要包含服務器的域名     

3. 在完成連接的代理方法中驗證用戶密碼,連接完成後XMPPStream的isConnect屬性爲YES     

4. 在驗證代理方法中判斷用戶是否登錄成功        

5. 上線或者下線成功後,向服務器發送Presence數據,以更新用戶在服務器的狀態

各部分的實現代碼如下:

  • 初始化 XMPPStream 並設置代理: 

    -(void)setupXMPPStream{
    
       _xmppStream = [[XMPPStream alloc] init];
    
       // 設置代理
       [_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    }

    func buildStream(){

            xs = XMPPStream()

            xs?.addDelegate(self, delegateQueue: dispatch_get_main_queue())

        }

  • 連接到服務器 

    -(void)connectToHost{
       NSLog(@"開始連接到服務器");
       if (!_xmppStream) {
           [self setupXMPPStream];
       }
    
       // 設置登錄用戶JID
       //resource 標識用戶登錄的客戶端 iphone android
    
       XMPPJID *myJID = [XMPPJID jidWithUser:@"aaa" domain:@"bourne-mbp.local" resource:@"iphone" ];
       _xmppStream.myJID = myJID;
    
       // 設置服務器域名
       _xmppStream.hostName = @"bourne-mbp.local";//不僅可以是域名,還可是IP地址
    
       // 設置端口 如果服務器端口是5222,可以省略
       _xmppStream.hostPort = 5222;
    
       // 連接
       NSError *err = nil;
       if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){
           NSLog(@"%@",err);
       }
    
    }

    func connect() -> Bool{

            buildStream()

            

            //通道已連接

            if (xs!.isConnected()){

                return true

            }

            

            let userNmae = NSUserDefaults.standardUserDefaults().stringForKey(Constants.UserName)

            

            let server = NSUserDefaults.standardUserDefaults().stringForKey(Constants.Server)

            

            if let userName = userNmae,server = server{

                

                me.name = userName

                me.domain = Constants.Domain

                

                //通道的用戶名

                xs!.myJIDXMPPJID.jidWithString(me.fullName)

                

                xs!.hostName = server

                

                return xs!.connectWithTimeout(5000, error: nil)

            }

            

            return false

        }

  • 連接成功後發送密碼驗證 

    -(void)sendPwdToHost{
       NSLog(@"再發送密碼授權");
       NSError *err = nil;
       [_xmppStream authenticateWithPassword:@"123456" error:&err];
       if (err) {
           NSLog(@"%@",err);
       }
    }

    //連接成功

        func xmppStreamDidConnect(sender: XMPPStream!) {

            //驗證密碼

            let password = NSUserDefaults.standardUserDefaults().stringForKey(Constants.Password)

            xs!.authenticateWithPassword(password, error: nil)

        }

  • 授權成功後,發送 在線 消息 

    #pragma mark  授權成功後,發送"在線" 消息
    -(void)sendOnlineToHost{
    
       NSLog(@"發送 在線 消息");
       XMPPPresence *presence = [XMPPPresence presence];
       NSLog(@"%@",presence);
    
       [_xmppStream sendElement:presence];
    }

    func xmppStreamDidAuthenticate(sender: XMPPStream!) {

            //上線

            goOnline()

            me.presenceType = Constants.Available

            statusDelegate?.meOn(me)

    }

    func goOnline(){

            var available = XMPPPresence()

            xs?.sendElement(available)

        }

需要實現的幾個代理方法

#pragma mark 與主機連接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
    NSLog(@"與主機連接成功");

    // 主機連接成功後,發送密碼進行授權
    [self sendPwdToHost];
}

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

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

    [self sendOnlineToHost];
}

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

//連接成功

    func xmppStreamDidConnect(sender: XMPPStream!) {

        //驗證密碼

        let password = NSUserDefaults.standardUserDefaults().stringForKey(Constants.Password)

        xs!.authenticateWithPassword(password, error: nil)

    }

    //斷開連接

    func xmppStreamDidDisconnect(sender: XMPPStream!, withError error: NSError!) {

        me.presenceType = Constants.Unavailable

        statusDelegate?.meOff(me)

    }

    //驗證成功

    func xmppStreamDidAuthenticate(sender: XMPPStream!) {

        //上線

        goOnline()

        me.presenceType = Constants.Available

        statusDelegate?.meOn(me)

    }

    //驗證失敗

    func xmppStream(sender: XMPPStream!, didNotAuthenticate error: DDXMLElement!) {

        me.presenceType = Constants.Unavailable

        NSUserDefaults.standardUserDefaults().setBool(false, forKey: Constants.AutoLogin)

        

        let alert = UIAlertView(title: "警告", message: "賬號或密碼錯誤.", delegate: self, cancelButtonTitle: "確定")

        alert.show()

    }

註銷登錄

  • 發送 離線 信息 
  • 斷開連接 
-(void)logout{
    // 1." 發送 `離線` 消息"
    XMPPPresence *offline = [XMPPPresence presenceWithType:@"unavailable"];
    [_xmppStream sendElement:offline];

    // 2. 與服務器斷開連接
    [_xmppStream disconnect];
}

func disConnect(){

        if let xs = xs{

            if xs.isConnected(){

                goOffline()

                xs.disconnect()

            }

        }

}

func goOffline(){

        var unavailable = XMPPPresence(name: Constants.Unavailable)

        xs?.sendElement(unavailable)

}

三、註冊

  • 與登錄一樣,首先發送帳號建立連接 

  • 連接成功後,發送註冊的密碼 

  • 註冊成功後,框架會通知代理 

實現以下代理方法

- (void)xmppStreamDidRegister:(XMPPStream *)sender {
    NSLog(@"註冊成功");

    if (_resultBlock) {
        _resultBlock(BWXMPPLoginResultSuccessed);
    }
}

- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
    if (_resultBlock) {
        _resultBlock(BWXMPPLoginResultFailure);
    }
}

四、用戶信息

XMPP是面向模塊的,每一個大的動能都屬於某一個模塊,需要使用時,就在頭文件中將其暴露出來(原本是被註釋了的)。 

  1. 工作原理: 

    添加用戶信息模塊之後,XMPPFramework框架會自動從服務器獲取用戶信息,並使用CoreData保存到本地的數據庫中,使用XMPPvCardTempModule可以訪問數據 

  2. XMPPFramework.h中將以下的頭文件前面的註釋去掉: 

    // 電子名片模塊
    #import "XMPPvCardTempModule.h"
    #import "XMPPvCardCoreDataStorage.h"
    
    // 頭像模塊
    #import "XMPPvCardAvatarModule.h"
    Swift中在橋接頭文件中添加以上引用。
  3. 初始化模塊 

    //添加電子名片模塊
    _vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
    _vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];
    
    //激活
    [_vCard activate:_xmppStream];
    
    //添加頭像模塊
    _avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];
    [_avatar activate:_xmppStream];
  4. 應用 

    //xmpp提供了一個方法,直接獲取個人信息
    XMPPvCardTemp *myVCard =[WCXMPPTool sharedWCXMPPTool].vCard.myvCardTemp;
    
    // 設置頭像
    if(myVCard.photo){
     self.haedView.image = [UIImage imageWithData:myVCard.photo];
    }
    
    // 設置暱稱
    self.nicknameLabel.text = myVCard.nickname;

五、好友

與用戶信息模塊相似,添加相應的好友花名冊模塊即可。

  1. 頭文件 

    // 花名冊模塊
    #import "XMPPRoster.h"
    #import "XMPPRosterCoreDataStorage.h"
  2. 初始化 

    // 添加花名冊模塊【獲取好友列表】
    _rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
    _roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];
    [_roster activate:_xmppStream];
  3. 應用 

    //使用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) {
     WCLog(@"%@",err);
    }
    • 注意:使用NSFetchedResultsController並設置代理,如果數據庫的內容發生了變化,這個類會自動通知代理,就可以設置界面的數據,做到實時更新。

六、消息

  1. 頭文件 

    • 注意:這幾個頭文件沒在XMPPFramework.h文件中,需要自己添加
    // 消息模塊
    #import "XMPPMessageArchiving.h"
    #import "XMPPMessageArchivingCoreDataStorage.h"
  2. 初始化 

    // 添加聊天模塊
    _msgStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
    _msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgStorage];
    [_msgArchiving activate:_xmppStream];
  3. 應用 

    // 上下文
    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) {
      WCLog(@"%@",err);
    }

七、文件傳送(圖片,音頻)

  1. 原理分析 

    • 使用base64將文件轉化爲字符串,然後再通過XMPPFramework傳輸。
    • 先將文件上傳到服務器,再將文件網址通過XMPPFramework轉輸給好友,好友收到後再自行下載文件。 
  1. 難點解析 

    • 需要給XMPPFramework的``數據體添加一個信息類型字段。
    XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];
    
    //text 純文本
    //image 圖片
    [msg addAttributeWithName:@"bodyType" stringValue:bodyType];
    
    // 設置內容
    [msg addBody:text];
    NSLog(@"%@",msg);
    [[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
    • 根據消息類型解析消息 

    ```objc
    XMPPMessageArchiving_Message_CoreDataObject *msg = _resultsContr.fetchedObjects[indexPath.row];

// 判斷是圖片還是純文本
NSString *chatType = [msg.message attributeStringValueForName:@"bodyType"];
if ([chatType isEqualToString:@"image"]) {
    //下圖片顯示
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:msg.body] placeholderImage:[UIImage imageNamed:@"DefaultProfileHead_qq"]];
    cell.textLabel.text = nil;
} else if ([chatType isEqualToString:@"text"]){

    //顯示消息
    if ([msg.outgoing boolValue]) {//自己發
        cell.textLabel.text = msg.body;
    }else{//別人發的
        cell.textLabel.text = msg.body;
    }

    cell.imageView.image = nil;
}
```

聲明:

  1. 以上內容屬於本人整理的筆記,如有錯誤的地方希望能告訴我,大家共同進步。 

  2. 以上內容有些段落或語句可能是本人從其他地方Copy而來,如有侵權,請及時告訴我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章