iOS XMPP: Openfire + Spark 实现即时通讯

一、XMPP简介

XMPP = The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议)

XMPP 协议是基于 XML 流同服务器以及客户端交互的, 首先就要用到 XMPPStream

导入 XMPPFramework 框架

XMPPFramework这个框架很大, 东西也很多, 打开他的文件目录就可以看到CoreExtension里面的一些 API

二、项目代码

XMPPManager.h

//枚举,用于区别登陆还是注册

typedef NS_ENUM(NSInteger , ConnectToServerPurpose) {

    //枚举值

    ConnectToServerPurposeLogin ,

    ConnectToServerPurposeRegister

};

typedefvoid(^LoginResult)(NSString*loginResult);

typedefvoid(^RegisterResult)(NSString*RegisterResult);

typedefvoid(^Refresh)();

@interface XMPPManager : NSObject<XMPPStreamDelegate,XMPPRosterDelegate,XMPPRosterMemoryStorageDelegate,XMPPReconnectDelegate,XMPPvCardTempModuleDelegate>

@property(nonatomic,strong)TabBarViewController *tabbarVC;

@property (nonatomic,copy)LoginResult loginResult;

@property (nonatomic,copy)RegisterResult registerResult;

@property (nonatomic,strong)NSString *password;

//将枚举设成属性

@property (nonatomic,assign)ConnectToServerPurpose connectToServerPurpose;

//通信通道,用于数据传输,要引入XMPPFramework.h头文件

@property (nonatomic, strong) XMPPStream *xmpp_stream;

//花名册,用于获取好友

@property (nonatomic,strong)XMPPRoster *xmppRoster;

@property (nonatomic,strong)XMPPvCardTempModule *xmppvCardTempModule;

@property (nonatomic,strong)XMPPReconnect *xmppReconnect;

@property (nonatomic,strong)XMPPAutoPing *xmppAutoPing;

//好友列表数组

@property (nonatomic,strong)NSMutableArray *friendArr;

//刷新列表block

@property (nonatomic,copy)Refresh refresh;

//信息归档对象

@property (nonatomic,strong)XMPPMessageArchiving *xmppMessageArchiving;

//数据管理器(coredata)

@property (nonatomic,strong)NSManagedObjectContext *contextXMPP;

//单例

+ (XMPPManager*)shareInstance;

//登录

- (void)loginWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithLoginResult:(LoginResult)loginResult;

//注册

- (void)registerWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithRegisterResult:(RegisterResult)registerResult;

//添加好友

- (void)addFriendActionWithFriendName:(NSString *)friendName;

//上线

- (void)goOnline;

//离线

- (void)offline;

//修改密码

- (void)changePasswordNewPassword:(NSString*)newPassword;

XMPPManager.m

@implementation XMPPManager

//XMPP相关信息

#define kHostName @"www.----.com"

#define kHostPort    5222

#define kDomin @"www.----.com"

#define kResource @"Smack"

#define CHATTYPE @"chat"                //消息类型

+ (XMPPManager*)shareInstance {

    staticXMPPManager*manager =nil;

    staticdispatch_once_tonceToken;

    dispatch_once(&onceToken, ^{

        manager = [[XMPPManageralloc]init];

    });

    returnmanager;

}

//懒加载好友列表

- (NSMutableArray *)friendArr {

    if(_friendArr==nil) {

        self.friendArr= [NSMutableArrayarray];

    }

    return _friendArr;

}

- (instancetype)init

{

    self= [superinit];

    if(self) {

        //初始化通信通道

        self.xmpp_stream = [[XMPPStream alloc]init];

        //给通信通道设置服务器IP地址

        self.xmpp_stream.hostName =kHostName;

        //给通信通道设置端口

        self.xmpp_stream.hostPort =kHostPort;

        self.xmpp_stream.enableBackgroundingOnSocket =YES;

        //给通信通道设置代理

        [self.xmpp_stream addDelegate:selfdelegateQueue:dispatch_get_main_queue()];

        //创建花名册数据存储对象

        XMPPRosterCoreDataStorage *xmppRosterStorage = [XMPPRosterCoreDataStorage sharedInstance];

        //创建花名册并指定了存储对象

        self.xmppRoster = [[XMPPRoster alloc]initWithRosterStorage:xmppRosterStorage dispatchQueue:dispatch_get_main_queue()];

        //激活通信通道

        [self.xmppRoster activate:self.xmpp_stream];

        //添加代理

        [self.xmppRoster addDelegate:selfdelegateQueue:dispatch_get_main_queue()];

        //添加自动重连接

        [self xmppManageAutoPing];

        //单例,信息归档存储对象

        XMPPMessageArchivingCoreDataStorage *xmppMessageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];

        //创建信息归档对象

        self.xmppMessageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:xmppMessageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];

        //激活通信通道

        [self.xmppMessageArchiving activate:self.xmpp_stream];

        //创建数据管理器

        self.contextXMPP = xmppMessageArchivingCoreDataStorage.mainThreadManagedObjectContext;

    }

    return self;

}

-(void)xmppManageAutoPing{

    //1.autoPing 发送的时一个stream:ping 对方如果想表示自己是活跃的,应该返回一个pong

    self.xmppAutoPing = [[XMPPAutoPing alloc] init];

    //所有的Module模块,都要激活active

    [self.xmppAutoPing activate:self.xmpp_stream];

    //autoPing由于他回定时发送ping,要求对方返回pong,因此这个时间我们需要设置

    [self.xmppAutoPing setPingInterval:1000];

    //不仅仅是服务器来得响应;如果是普通的用户,一样会响应

    [self.xmppAutoPing setRespondsToQueries:YES];

    //这个过程是C---->S  ;观察 S--->C(需要在服务器设置)

    //2.autoReconnect 自动重连,当我们被断开了,自动重新连接上去,并且将上一次的信息自动加上去

    self.xmppReconnect = [[XMPPReconnect alloc] init];

    [self.xmppReconnect activate:self.xmpp_stream];

    [self.xmppReconnect setAutoReconnect:YES];

    [self.xmppReconnect addDelegate:selfdelegateQueue:dispatch_get_main_queue()];

}

//  重连机制的代理方法

-(BOOL)xmppReconnect:(XMPPReconnect *)sendershouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags{

    DBLog(@"开始尝试自动连接:%u", connectionFlags);

    return YES;

}

-(void)xmppReconnect:(XMPPReconnect *)senderdidDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags{

    DBLog(@"检测到意外断开连接:%u",connectionFlags);

}

#pragma mark- 连接服务器 -

//与服务器链接是一个长连接

- (void)connectToServerWithUserID:(NSString *)userID{

    //创建一个JID对象。每个JID对象代表的就是一个封装好的用户

    XMPPJID *jidItem = [XMPPJID jidWithUser:userID domain:kDominresource:kResource];

    //设置通信通道的JID

    self.xmpp_stream.myJID = jidItem;

    //如果通信通道正在连接或者已经连接

    if([self.xmpp_stream isConnecting] || [self.xmpp_stream isConnected]) {

        //1.发送下线状态

        XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];

        [self.xmpp_stream sendElement:presence];

        //2.断开连接

        [self.xmpp_stream disconnect];

    }

    //向服务器发送请求。连接成功会走代理方法

    NSError*error =nil;

    [self.xmpp_stream connectWithTimeout:-1error:&error];

    if(error !=nil) {

        DBLog(@"连接错误");

    }

}

#pragma mark - xmppStream的代理方法 -

//连接成功

- (void)xmppStreamDidConnect:(XMPPStream *)sender {

    DBLog(@"连接成功");

    if (self.connectToServerPurpose == ConnectToServerPurposeLogin) {

        //连接成功。就可以进行登录操作了,验证账号和密码是否匹配

        [self.xmpp_stream authenticateWithPassword:self.password error:nil];

    }

    if (self.connectToServerPurpose == ConnectToServerPurposeRegister) {

        //连接成功。就可以进行注册操作了,

        [self.xmpp_stream registerWithPassword:self.password error:nil];

    } 

}

//连接超时

- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {

    DBLog(@"连接超时");

}

#pragma mark- 验证的代理方法 -

//已经验证

- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {

    DBLog(@"验证成功");

    //发送一个上线状态

    XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];

    [self.xmpp_stream sendElement:presence];

    self.loginResult(@"loginSuccess");

}

//验证失败

- (void)xmppStream:(XMPPStream *)senderdidNotAuthenticate:(DDXMLElement *)error {

    DBLog(@"验证失败");

    DBLog(@"%@",error);

    self.loginResult(@"loginFail");

}

#pragma mark- 注册结果的代理方法 -

//注册成功了

- (void)xmppStreamDidRegister:(XMPPStream *)sender{

    DBLog(@"注册成功");

    self.registerResult(@"registerSuccess");

}

//注册失败了

- (void)xmppStream:(XMPPStream *)senderdidNotRegister:(DDXMLElement *)error{

    DBLog(@"注册失败了");

    self.registerResult(@"registerFail");

}

//登录方法

- (void)loginWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithLoginResult:(LoginResult)loginResult {

    self.connectToServerPurpose = ConnectToServerPurposeLogin;

    self.loginResult= loginResult;

    self.password= password;

    //连接服务器

    [self connectToServerWithUserID:userID];

    self.friendArr=nil;

}

//注册方法

- (void)registerWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithRegisterResult:(RegisterResult)registerResult {

    self.connectToServerPurpose = ConnectToServerPurposeRegister;

    self.password= password;

    self.registerResult= registerResult;

    //连接服务器

    [self connectToServerWithUserID:userID];

}

#pragma mark- 花名册代理方法 -

//开始检索

- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{

    DBLog(@"开始检索了");

    [self.friendArr removeAllObjects];

    XMPPJID *jiddata = [XMPPJID jidWithString:@""];

    XMPPJID *jiddata1 = [XMPPJID jidWithString:@""resource:@""];

    XMPPJID *jiddata2 = [XMPPJID jidWithUser:@""domain:@""resource:@""];

}

//结束检索

- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{

    DBLog(@"结束检索");

    DBLog(@"%ld",self.friendArr.count);

}

// 添加好友同意后,会进入到此代理

- (void)xmppRoster:(XMPPRoster *)senderdidReceiveRosterPush:(XMPPIQ *)iq{

    DBLog(@"didReceiveRosterPush");

    DBLog(@"添加成功!!!didReceiveRosterPush -> :%@",iq.description);

    DDXMLElement *query = [iq elementsForName:@"query"][0];

    DDXMLElement *item = [query elementsForName:@"item"][0];

    NSString*subscription = [[item attributeForName:@"subscription"] stringValue];

    // 对方请求添加我为好友且我已同意

    if([subscriptionisEqualToString:@"from"]) {// 对方关注我

        DBLog(@"我已同意对方添加我为好友的请求");

    }

    // 我成功添加对方为好友

    elseif([subscriptionisEqualToString:@"to"]) {// 我关注对方

        DBLog(@"我成功添加对方为好友,即对方已经同意我添加好友的请求");

    }elseif([subscriptionisEqualToString:@"remove"]) {

        // 删除好友

        DBLog(@"删除好友");

        [selfloginWithUserID:[GVUserDefaults standardUserDefaults].Name withPassword:[GVUserDefaults standardUserDefaults].password withLoginResult:^(NSString *loginResult) {

            if([loginResult isEqualToString:@"loginSuccess"]) {

                DBLog(@"登录成功");

                DBLog(@"%@",self.friendArr);

            }else{

                DBLog(@"登录失败");

            }

        }];

    }

}

- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)senderwithVersion:(NSString*)version{

    DBLog(@"xmppRosterDidBeginPopulating");

}

- (void)xmppRoster:(XMPPRoster *)senderdidReceiveRosterItem:(NSXMLElement *)item{

    DBLog(@"didReceiveRosterItem");

    NSString*subscriptionValue = [[item attributeForName:@"subscription"]stringValue];

    //  展示互为好友的信息

    if([subscriptionValueisEqualToString:@"both"]) {

        NSString*jidStr = [[item attributeForName:@"jid"]stringValue];

        //  封装成XMPPJID

        XMPPJID *friendJID = [XMPPJID jidWithString:jidStr];

        //  如果数组中已经包含了这个好友对象,就不再进行添加了

        if([self.friendArr containsObject:friendJID]) {

            return;

        }

        //  添加到数据源数组中

        [self.friendArr addObject:friendJID];

    }

    DBLog(@"%ld",self.friendArr.count);

}

#pragma mark- 添加好友 -

- (void)addFriendActionWithFriendName:(NSString *)friendName {

    //把账号封装成JID对象

    XMPPJID *addJID = [XMPPJID jidWithUser:friendName domain:kDominresource:kResource];

    //发送好友请求

    [self.xmppRoster addUser:addJID withNickname:nil];

//    [self.xmppRoster subscribePresenceToUser:addJID];

}

#pragma mark- 接受到好友请求 -

- (void)xmppRoster:(XMPPRoster *)senderdidReceivePresenceSubscriptionRequest:(XMPPPresence *)presence{

    DBLog(@"接受到好友请求");

    DBLog(@"%@",presence);

    DBLog(@"%@",presence.from);

}

// 如果不是初始化同步来的roster,那么会自动存入我的好友存储器

- (void)xmppRosterDidChange:(XMPPRosterMemoryStorage *)sender

{

    DBLog(@"xmppRosterDidChange");

}

//收到对方取消定阅我得消息

- (void)xmppStream:(XMPPStream *)senderdidReceivePresence:(XMPPPresence *)presence

{

    if([presence.type isEqualToString:@"unsubscribe"]) {

        //从我的本地通讯录中将他移除

        [self.xmppRoster removeUser:presence.from];

        //将此好友从会话列表中移除

        ReplyModel *model = [[ReplyModel alloc]init];

        model.name = presence.from.user;

        [[DBManager sharedInstance] deleteReply:model];

        if(self.refresh) {

            self.refresh();

        }

    }

}

#pragma mark - 接受了消息的代理方法 -

//接收了信息

- (void)xmppStream:(XMPPStream *)senderdidReceiveMessage:(XMPPMessage *)message{

    DBLog(@"XMPPManager:接受了信息");

}

//上线

- (void)goOnline

{

    // 发送一个 默认值avaliable 在线 服务器收到空的presence 也会认为是avaliable

    // status ---自定义的内容,可以是任何的。

    // show 是固定的,有几种类型 dnd、xa、away、chat,在方法XMPPPresence 的intShow中可以看到

    XMPPPresence *presence = [XMPPPresence presence];

    [presence addChild:[DDXMLNode elementWithName:@"status"stringValue:@"我上线咯"]];

    [presence addChild:[DDXMLNode elementWithName:@"show"stringValue:@"xa"]];

    [_xmpp_stream sendElement:presence];

}

//离线

- (void)offline

{

    XMPPPresence *off = [XMPPPresence presenceWithType:@"unavailable"];

    [_xmpp_stream sendElement:off];

    //断开连接

    [_xmpp_stream disconnect];

}

//删除好友

-(void)removeFriend:(XMPPJID*)jid{

    XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribe"to:[jid bareJID]];

    [presence addAttributeWithName:@"id"stringValue:jid.user];

    [_xmpp_stream sendElement:presence];

}

//修改用户密码

-(void)changePasswordNewPassword:(NSString*)newPassword{

    /*

     bill

     newpass

     */

    NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];

    [iq addAttributeWithName:@"type"stringValue:@"set"];

    [iq addAttributeWithName:@"to"stringValue:@"ubuntu-dev"];

    [iq addAttributeWithName:@"id"stringValue:@"change1"];  //@"change1"确定id唯一就行

    NSXMLElement *queryElement = [NSXMLElement elementWithName:@"query"xmlns:@"jabber:iq:register"];

    [iq addChild:queryElement];

    NSString*userNameStr = [GVUserDefaults standardUserDefaults].Name;

    NSXMLElement *username = [NSXMLElement elementWithName:@"username"];

    [username setStringValue:userNameStr];

    NSXMLElement *password = [NSXMLElement elementWithName:@"password"];

    NSLog(@"newPassword_______:%@",newPassword);

    [password setStringValue:newPassword];

    [queryElement addChild:username];

    [queryElement addChild:password];

    //NSLog(@"iq__%@",iq.description);//打印下XML流检查是否拼接正确

    [[XMPPManager shareInstance].xmpp_stream

     sendElement:iq];//发送{XMPPStream被我封装进单例类XMPPService里}

//    [[XMPPManager shareInstance].xmpp_stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//设置代理

}

- (BOOL)xmppStream:(XMPPStream *)senderdidReceiveIQ:(XMPPIQ *)iq{

//    NSLog(@"__%@",iq.description);

//    <iq xmlns="jabber:client" type="result" id="change1" from="ubuntu-dev" to="13333333333@ubuntu-dev/870efdcd"></iq>

    NSString*iqTypePWD = [[iq attributeForName:@"type"]stringValue];

    NSString*iqIDPWD = [[iq attributeForName:@"id"]stringValue];

//    NSLog(@"iqTypePWD:%@___iqTypePWD:%@",iqTypePWD,iqIDPWD);

    if ([iqTypePWD isEqualToString:@"result"] && [iqIDPWD isEqualToString:@"change1"]) {  //进行判断只有type="result" id="change1"时,密码修改成功

        NSLog(@"XMPP密码修改成功!");

    }

    return YES;

}

在这个 demo 中

可以看出, xmppStream 初始化分三个步骤

1、初始化(创建流对象)

2、设置服务器 IP和端口号(告诉流往哪里连接)

3、添加代理(成功与否通过代理回调)

· 创建流对象成功后,发起连接之前,你还需要将 jid 绑定到流上,这样才算是沟通用户和服务器

· 但是无论账号填写什么,甚至是 nil,流也是会连接成功的,因为流连接只需要域名和端口号即可

· 那登录成功的密码验证呢? 则是在流连接成功后, 通过流发送验证请求, 如果验证通过,这通过代理回调, 在方法内部再调用 block, 实现C端(控制器端)的操作

· 在控制器端完成 block 的回调

XMPP

消息发送

XMPPMessage *message = [XMPPMessage messageWithType:CHATTYPE to:self.friendJID];

[message addBody:@"发送的消息"];

[[XMPPManager shareInstance].xmpp_stream sendElement:message];

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354