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而来,如有侵权,请及时告诉我。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值