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框架的用法和思路,不完善处请见谅!如本文或代码中有任何错或不规范处请指出,谢谢!

时间: 2024-10-12 18:30:54

XMPP学习记录之实战篇的相关文章

python学习记录第五篇--遍历目录

#coding=utf-8'''@author: 简单遍历目录删除文件的小程序'''import os#查找文件操作def findFile(path): fileList=[] for rootPath,subRoot,fileName in os.walk(path): for sub in fileName: if os.path.isfile(os.path.join(rootPath,sub)): k=os.path.splitext(sub)[1].lower() if k in (

python学习记录第四篇--数据库

只要用到MySQLdb,使用时请先安装MySQLdb,百度上可以下载! #coding=utf-8'''@author: 使用python操作MySQL数据库'''import MySQLdb#import MySQLdb.cursorsconn=MySQLdb.connect(user='root',passwd='root') #connect共三个值,user,passwd,host,无密码且连接本地数据库时,可以都为空.cur=conn.cursor() #创建游标,使用游标进行数据库操

Maven学习记录(三)--实战引入Spring支持

一.创建项目 maven项目在IDEA下创建是相当容易 然后给定项目坐标,确定即可 二.完善项目目录结构 IDEA创建完项目结构和标准的maven项目有些差异,这个时候就需要我们手动调整一下 更改前结构 更改后 三.引入spring支持 引入框架无非三步走: 1. 引入架包 2. 创建配置文件 3. 加载配置文件 接下来的步骤也是按照这三步走方法来的 首先是pom.xml文件,增加spring和springMVC需要的包 <properties> <!-- springframe 版本控

Activiti框架学习记录-01

Activiti框架学习记录-01 本篇主要是Activiti工作流框架的学习记录,以及对于该框架的基本使用和一些浅显的理解: 1.工作流框架基本概念 2.在eclipse中使用工作流框架 3.创建工作框架 4.使用框架提供API,操作框架实现业务逻辑 1.工作流框架基本概念(摘自百度百科) 工作流(Workflow),就是"业务过程的部分或整体在计算机应用环境下的自动化",它主要解决的是"使在多个参与者之间按照某种预定义的规则传递文档.信息或任务的过程自动进行,从而实现某个

ELK stack 学习记录

ELK日志分析平台学习记录 首先ELK主要指elasticsearch .logstash 和kibana,三个开源软件组合而成的一套日志平台解决方案.可以将平时收集到的日志,通过前台展示出来,并且可以加以分析,理论上可以解放劳动力(再也不用干上生产取日志这种活了--很搓). 最近在研究ELKstack日志分析平台,网上相关的中文资料不多.所以呢也就写了这篇文章将自己的一些学习认识总结记录下来,基本偏实战,概念理论较少,概念这块,我想以后可以再开一篇文章来做一个阐述总结. 这篇文章中会先讲一下搭

Android 自定义ViewGroup 实战篇 -&gt; 实现FlowLayout

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自[张鸿洋的博客] 1.概述 上一篇已经基本给大家介绍了如何自定义ViewGroup,如果你还不了解,请查看:Android 手把手教您自定ViewGroup ,本篇将使用上篇介绍的方法,给大家带来一个实例:实现FlowLayout,何为FlowLayout,如果对Java的Swing比较熟悉的话一定不会陌生,就是控件根据ViewGroup的宽,自动的往右

jqGrid 学习笔记整理——进阶篇(二)

jqGrid 学习笔记整理--进阶篇(二 ) 本篇开始正式与后台(java语言)进行数据交互,使用的平台为 JDK:java 1.8.0_71 myEclisp 2015 Stable 2.0 Apache Tomcat-8.0.30 Mysql 5.7 Navicat for mysql 11.2.5(mysql数据库管理工具) 一.数据库部分 1.创建数据库 使用Navicat for mysql创建数据库(使用其他工具或直接使用命令行暂不介绍) 2.创建表 双击打开上步创建数据库--右击T

C# Xamarin移动开发项目实战篇

一.课程介绍 在前面阿笨的<C# Xamarin移动开发基础进修篇>课程中,大家已经熟悉和了解了Xamarin移动App开发的基础知识和原理.本次分享课<C# Xamarin移动开发项目实战篇>,阿笨将直接带领大家进入Xamarin for android的实战项目环节,真真体验一下xamarin开发的魅力吧. 由于阿笨学习Xamarin也是"半路出家","赶鸭子上架"的状态,视频教学中关于Xamarin for Android的知识点难免有

彻底征服 Spring AOP 之 实战篇

接上一小节彻底征服 Spring AOP 之 理论篇 Spring AOP 实战 看了上面这么多的理论知识, 不知道大家有没有觉得枯燥哈. 不过不要急, 俗话说理论是实践的基础, 对 Spring AOP 有了基本的理论认识后, 我们来看一下下面几个具体的例子吧.下面的几个例子是我在工作中所遇见的比较常用的 Spring AOP 的使用场景, 我精简了很多有干扰我们学习的注意力的细枝末节, 以力求整个例子的简洁性. 下面几个 Demo 的源码都可以在我的 Github 上下载到. HTTP 接口