美文网首页
iOS开发之-XMPP协议及时通讯

iOS开发之-XMPP协议及时通讯

作者: 橘子味草莓 | 来源:发表于2019-06-27 10:08 被阅读0次

刚好最近在做一个项目,里面有用到XMPP通讯协议,其实相较于其他通讯协议,XMPP比较厚重,不适合以即时通讯为主的APP,只适用于小型,用户量少的APP。

下面简单讲一下XMPP的实现过程:

首页,导入XMPPFramework这个库,如果是cocoapods导入的话,记得在pod‘XMPPFramework’库之前加一句,use_frameworks!,这样就不会有import报错的情况

然后,创建一个XMPPSocket的单例

接着就是XMPP的相关操作

1:创建XMPPStream流

2:定时发送心跳包XMPPAutoPing,心跳包的作用就是告诉服务器客户端还在连接中

3:重连机制,在Scoket断开连接后,立刻去重新连接,同时也要检测当前网络状况,如果是弱网或者是断网的情况下,就不用去响应重新连接的方法,网络正常的话就要去重新连接,给一个int,重连int++,int超过十次就断开连接,当然你也可以选择一直练,但是这样是不建议的,

4:流管理,

XMPPStreamManagementMemoryStorage和XMPPStreamManagement

接入流管理模块,是用于流恢复和消息确认

5:好友模块,XMPPRoster,获取好友列表,添加好友,获取好友节点,获取好友当前状态(上线,离开,忙碌,下线)

6:消息模块,XMPPMessageArchivingCoreDataStorage,用于本地储存消息,服务器只是一个中介,只会存储离线消息,所以消息记录还是需要我们来做本地数据库的缓存,当然我们最常用的就是FMDB框架,所以你可以根据需要自己去封装一个工具类

到这里,常用的一些类都已经提到,接下来就要写一些常用方法,例如登录,发送消息,添加好友,退出登录这样的方法,还有一个更重要的就是,写一个代理方法,收到消息就执行这个代理,可以在项目里任意地方需要更新UI的地方去遵循这个代理。

这样XMPPSocket的工具类就写的差不多了,当然如果你有更多的需要也可以慢慢添加。

#define DO_MAIN @"timeremind.cn"

@interfaceXMPPSocketMannager()

{

    /** 重新登录次数,初始化为0 */

    NSIntegerreloginCount;

    /** 重连次数,初始化为0 */

    NSIntegerreconnectCount;

    /** ping超时次数,初始化为0 */

    NSIntegerpingTimeoutCount;

    /** 收消息的队列,是一个串行队列 */

    dispatch_queue_t_streamQueue;

}

//没有网络的时候检测网络定时器

@property(nonatomic,strong)NSTimer*netWorkTestingTimer;

@end

@implementationXMPPSocketMannager

#pragma mark  -私有方法

#pragma mark 初始化XMPPStream

static XMPPSocketMannager *sInstance;

// 单例

+ (instancetype) sharedManager {

    staticdispatch_once_tonceToken;

    dispatch_once(&onceToken, ^{

        sInstance = [XMPPSocketMannager new];

        // 设置日志打印

        [DDLog addLogger:[DDTTYLogger sharedInstance]];

    });

    return sInstance;

}

// 懒加载XMPPStream

- (XMPPStream*)xmppStream {

    if(nil==_xmppStream) {

        // 创建

        _xmppStream = [[XMPPStream alloc] init];

        // 设置属性

        // ip

        _xmppStream.hostName=DO_MAIN;

        // 端口

        //XMPPStreamStartTLSPolicyRequired

        //XMPPStreamStartTLSPolicyAllowed

        _xmppStream.hostPort=5222;

        _xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyAllowed;

        // 设置代理--多播代理

        [_xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];

        //心跳包

        _xmppAutoPing = [[XMPPAutoPing alloc] init];

        _xmppAutoPing.pingInterval = 10.f; // 心跳包间隔

        [_xmppAutoPing activate:_xmppStream];

        [_xmppAutoPing addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

        //重连机制

        _xmppReconnect = [[XMPPReconnect alloc] init];

        _xmppReconnect.autoReconnect = YES;

        _xmppReconnect.reconnectDelay = 0.f;// 一旦失去连接,立马开始自动重连,不延迟

        _xmppReconnect.reconnectTimerInterval = 3.f;// 每隔3秒自动重连一次

        [_xmppReconnect activate:_xmppStream];

        [_xmppReconnect addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];

        //接入流管理模块,用于流恢复跟消息确认

        _storage = [XMPPStreamManagementMemoryStorage new];

        _xmppStreamManagement = [[XMPPStreamManagement alloc] initWithStorage:_storage];

        _xmppStreamManagement.autoResume = YES;

        [_xmppStreamManagement addDelegate:self delegateQueue:dispatch_get_main_queue()];

        [_xmppStreamManagement activate:self.xmppStream];

        //接入好友模块,可以获取好友列表

        _xmppRosterMemoryStorage = [[XMPPRosterMemoryStorage alloc] init];

        _xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:_xmppRosterMemoryStorage];

        [_xmppRoster activate:self.xmppStream];

        [_xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];

        //接入消息模块,将消息存储到本地

        _xmppMessageArchivingCoreDataStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];

        _xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_xmppMessageArchivingCoreDataStorage dispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 9)];

        [_xmppMessageArchiving activate:self.xmppStream];

    }

    return _xmppStream;

}

// 登陆

- (void)loginWithName:(NSString*)name andPassword:(NSString*)password {

    // 创建一个JID

    XMPPJID *jid = [XMPPJID jidWithUser:name domain:DO_MAIN resource:@"eyetouch"];

    // 设置MyJID

    [self.xmppStreamsetMyJID:jid];

    // 设置密码

    self.password= password;

    // 连接到服务器

    [self connectServer];

}

// 连接到服务器

- (void)connectServer {

    // 异常信息

    NSError*error =nil;

    // 连接服务器

    [self.xmppStream connectWithTimeout:15.0f error:&error];

    // 判断是否连接成功

    if(error) {

        XPLog(@"登录失败,这里可以实现掉线自动重连,要注意以下几点");

        XPLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");

        XPLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");

        //判断网络环境

        if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){

            //没有网络

            //开启网络检测定时器

            [self noNetWorkStartTestingTimer];

        }else{

            //有网络

            //连接失败就重连

            [self reconnectImmediately];

        }

    }

}

//这里是因为服务器用的是自签证书,并且需要我们加密登录,也就是在服务端那里能看见一个锁的图标

- (void)xmppStream:(XMPPStream*)sender willSecureWithSettings:(NSMutableDictionary*)settings

{

    settings[GCDAsyncSocketManuallyEvaluateTrust] = @(YES);

    [settingssetObject:self.xmppStream.myJID.domain forKey:(NSString *)kCFStreamSSLPeerName];

}

- (void)xmppStream:(XMPPStream*)sender

   didReceiveTrust:(SecTrustRef)trust

 completionHandler:(void(^)(BOOLshouldTrustPeer))completionHandler

{

    completionHandler(YES);

}

#pragma mark 代理方法,连接成功之后回调

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

    XPLog(@"🍎xmpp连接服务器成功");

    // 错误

    NSError*error =nil;

    // 登陆

    [self.xmppStream authenticateWithPassword:self.password error:&error];

}

#pragma mark 连接失败

-(void)xmppStreamDidDisconnect:(XMPPStream*)sender withError:(NSError*)error

{

    XPLog(@"🍎xmpp连接服务器失败,失败原因:%@",error);

}

- (void)xmppStreamConnectDidTimeout:(XMPPStream*)sender

{

    XPLog(@"🍎xmpp连接服务器超时");

    [self reconnectImmediately];

}

//认证失败后的回调

-(void)xmppStream:sender didNotAuthenticate:(DDXMLElement*)error

{

    XPLog(@"🍎xmpp认证失败,用户名或密码错误:%@",error);

}

#pragma mark 代理方法,登陆成功之后告诉服务器我要出席

//认证成功后的回调

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

    XPLog(@"🍎xmpp认证成功");

    //如果成功,可以出席

    XMPPPresence *presence = [XMPPPresence presence];

    //添加出席状态

    [presenceaddChild:[DDXMLElement elementWithName:@"status" stringValue:@"在线"]];

    [presenceaddChild:[DDXMLElement elementWithName:@"priority" stringValue:@"1"]];

    //告诉服务器我要出席

    [self.xmppStreamsendElement:presence];

}

#pragma mark - 心跳包

#pragma mark - XMPPAutoPingDelegate

- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing*)sender{

    XPLog(@"🍎xmpp接收心跳:%@",sender);

    // 如果至少有1次超时了,再收到pong包,则清除超时次数

    if (pingTimeoutCount > 0) {

        pingTimeoutCount = 0;

    }

}

- (void)xmppAutoPingDidTimeout:(XMPPAutoPing*)sender {

    // 收到两次超时,就disconnect吧

    pingTimeoutCount++;

    if (pingTimeoutCount >= 2) {

        [selflogout];

    }

}

- (void)xmppAutoPingDidSendPing:(XMPPAutoPing*)sender

{

    XPLog(@"🍎xmpp发送心跳:%@",sender);

}

#pragma mark - XMPPReconnectDelegate

- (void)xmppReconnect:(XMPPReconnect*)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {

    XPLog(@"%u",connectionFlags);

    XPLog(@"🍎xmpp意外断开连接。");

    [self reconnectImmediately];

}

- (BOOL)xmppReconnect:(XMPPReconnect*)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {

    reconnectCount++;

    XPLog(@"🍎xmpp自动重连...第%@次",@(reconnectCount));

    if (reconnectCount >10) {

        [self.xmppReconnectstop];

    }

    return YES;

}

#pragma mark - 重连

- (void)reconnectImmediately {

    self.xmppReconnect.reconnectTimerInterval = 3.f;

    reconnectCount = 0;

    [self.xmppReconnect stop];

    [self.xmppReconnect manualStart];

}

#pragma mark - 下线操作

- (void)xmppStream:(XMPPStream*)sender didReceiveError:(NSXMLElement*)error {

    //

    NSString *conflict = [[error elementForName:@"conflict"] stringValue];

    if(conflict) {

        //下线操作

        [self.xmppStreamdisconnect];

        [SVProgressHUD showSuccessWithStatus:@"您已下线,请确认账户安全"];

        //跳转登录页

        [AcountItem removesid];

        AppDelegate *application = (AppDelegate *)[UIApplication sharedApplication].delegate;

        [applicationpushToLoginVC];

    }else{

        //其他原因连接失败随即重连

        [self reconnectImmediately];

    }

}

// 退出登陆

- (void) logout {

    // 用户状态类

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

    // 向服务器发送离线通知

    [self.xmppStreamsendElement:presence];

    // 断开连接

    [self.xmppStream disconnect];

}

/*

 接收消息

 */

- (void)xmppStream:(XMPPStream*)sender didReceiveMessage:(XMPPMessage*)message

{

    XPLog(@"🍎xmpp接收消息:%@",message);

    //消息处理

    dispatch_async(dispatch_get_main_queue(), ^{

        [MessageSocketMannager MessageChangeType:message];

    });

    if([self.delegaterespondsToSelector:@selector(XMPPSocketMannagerDidReceiveMessageWithString:)]) {

        [self.delegate XMPPSocketMannagerDidReceiveMessageWithString:message];

    }

}

/*

 发送消息

 */

- (void)sendMessage:(NSString*)message toUser:(NSString*)user WithType:(NSString*)type{

    NSDictionary *dict=@{@"content":message,@"sendTime":@([[FDefultTool GetNowTimes] longLongValue]),@"type":@([type integerValue]),@"headImage":[AcountItem getuseheadImgUrl],@"UserName":[AcountItem getuserName]};

    XMPPJID *jid = [XMPPJID jidWithUser:user domain:DO_MAIN resource:@"eyetouch"];

    XMPPMessage * messagedata=[XMPPMessage messageWithType:@"chat" to:jid];

    [messagedataaddBody:[FDefultToolconvertToJsonData:dict]];

    //没有网络

    if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)

    {

        //开启网络检测定时器

        [self noNetWorkStartTestingTimer];

    }

    else//有网络

    {

        if(self.xmppStream!=nil)

        {

            // 只有长连接OPEN开启状态才能调 send 方法,不然会Crash

            if([self.xmppStreamisConnected])

            {

                [self.xmppStreamsendElement:messagedata];

            }

            elseif([self.xmppStreamisConnecting])//正在连接

            {

                [SVProgressHUD showInfoWithStatus:@"在连接服务器中,重连后会去自动同步数据"];

            }

            elseif([self.xmppStreamisConnecting] || [self.xmppStreamisDisconnected])//断开连接

            {

                //调用 reConnectServer 方法重连,连接成功后 继续发送数据

                [selfreconnectImmediately];

            }

        }

        else

        {

            [selfconnectServer];//连接服务器

        }

    }

}

//没有网络的时候开始定时 -- 用于网络检测

- (void)noNetWorkStartTestingTimer{

    __weak __typeof(self)weakSelf = self;

    dispatch_main_async_safe(^{

        weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0target:weakSelf selector:@selector(noNetWorkStartTesting) userInfo:nilrepeats:YES];

        [[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];

    });

}

//定时检测网络

- (void)noNetWorkStartTesting{

    //有网络

    if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)

    {

        //关闭网络检测定时器

        [self destoryNetWorkStartTesting];

        //开始重连

        [self reconnectImmediately];

    }else{

        [selflogout];

    }

}

//取消网络检测

- (void)destoryNetWorkStartTesting{

    __weak __typeof(self)weakSelf = self;

    dispatch_main_async_safe(^{

        if(weakSelf.netWorkTestingTimer)

        {

            [weakSelf.netWorkTestingTimer invalidate];

            weakSelf.netWorkTestingTimer =nil;

        }

    });

}

/*

 好友状态

 available 上线

 away 离开

 do not disturb 忙碌

 unavailable 下线

 */

- (void)xmppStream:(XMPPStream*)sender didReceivePresence:(XMPPPresence*)presence {

    NSString*presenceType = [presencetype];

    NSString*presenceFromUser = [[presencefrom]user];

    if(![presenceFromUserisEqualToString:[[sendermyJID]user]]) {

        if([presenceTypeisEqualToString:@"available"]) {

            XPLog(@"上线");

        }elseif([presenceTypeisEqualToString:@"away"]) {

            XPLog(@"离开");

        }elseif([presenceTypeisEqualToString:@"do not disturb"]) {

            XPLog(@"忙碌");

        }elseif([presenceTypeisEqualToString:@"unavailable"]) {

            XPLog(@"下线");

        }

    }

}

// 收到好友请求执行的方法

-(void)xmppRoster:(XMPPRoster*)sender didReceivePresenceSubscriptionRequest:(XMPPPresence*)presence{

    //    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示:有人添加你" message:presence.from.user  delegate:self cancelButtonTitle:@"拒绝" otherButtonTitles:@"OK", nil];

    //    [alert show];

}

#pragma mark 开始检索好友列表的方法

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

    NSLog(@"🍎xmpp开始检索好友列表");

}

#pragma mark 正在检索好友列表的方法

-(void)xmppRoster:(XMPPRoster*)sender didRecieveRosterItem:(DDXMLElement*)item{

    NSMutableArray *array=[[NSMutableArray alloc]init];

    XPLog(@"🍎xmpp每一个好友都会走一次这个方法");

    //获得item的属性里的jid字符串,再通过它获得jid对象

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

    XMPPJID *jid = [XMPPJID jidWithString:jidStr];

    //是否已经添加

    if([arraycontainsObject:jid]) {

        return;

    }

    //将好友添加到数组中去

    [arrayaddObject:jid];

}

#pragma mark 好友列表检索完毕的方法

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

    XPLog(@"🍎xmpp好友列表检索完毕");

}

#pragma mark ReceiveIQ

- (BOOL)xmppStream:(XMPPStream*)sender didReceiveIQ:(XMPPIQ*)iq

{

    NSMutableArray *dataArr=[[NSMutableArray alloc]init];

    XPLog(@"%@: %@", THIS_FILE, THIS_METHOD);

    if ([@"result" isEqualToString:iq.type]) {

        NSXMLElement*query = iq.childElement;

        if([@"query"isEqualToString:query.name]) {

            NSArray*items = [querychildren];

            for(NSXMLElement*iteminitems) {

                NSString *jid = [item attributeStringValueForName:@"jid"];

                XMPPJID*xmppJID = [XMPPJIDjidWithString:jid];

                [dataArraddObject:xmppJID];

            }

        }

    }

    if([self.delegaterespondsToSelector:@selector(XMPPSocketMannagerDidFriendWithArr:)]) {

        [self.delegate XMPPSocketMannagerDidFriendWithArr:dataArr];

    }

    return NO;

}

//添加好友

- (void)XMPPAddFriendSubscribe:(NSString*)userName

{

    XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",userName,DO_MAIN]];

    [self.xmppRoster subscribePresenceToUser:jid];

}

//删除好友

- (void)XMPPremoveFriendWithName:(NSString*)userName

{

    XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",userName,DO_MAIN]];

    // 停止监听好友

    [self.xmppRoster unsubscribePresenceFromUser:jid];

    [self.xmppRosterremoveUser:jid];

}

暂时就是这些,另外如果有什么不明白可以评论问我

相关文章

  • iOS开发之-XMPP协议及时通讯

    刚好最近在做一个项目,里面有用到XMPP通讯协议,其实相较于其他通讯协议,XMPP比较厚重,不适合以即时通讯为主的...

  • XMPP (小明屁屁?) 麦盖鬼?

    一. 麦盖系XMPP? 1.XMPP (可扩展通讯和表示协议) 2.XMPP是一个机遇XML的及时通讯协议, 官方...

  • OSI七层模型

    TCP UDP HTTP HTTPS 协议 数据存储XMPP -及时通讯 框架coredatasocket OSI...

  • 2018-07-26

    Android即时通讯四种协议之一 XMPP协议 1.Android即时通讯是什么? 大多数及时通讯协议已经超过了...

  • XMPP与环信

    XMPP --> 环信1.XMPP是网络层基于TCP协议,数据层基于XML协议的即时通讯协议。所以要实现通讯的话,...

  • XMPP - 协议简介

    要学习基于XMPP协议的IM开发,首先要熟悉XMPP协议本身。 XMPP协议的组成主要的XMPP 协议范本及当今应...

  • 02-即时通讯-XMPP 简单介绍

    XMPP是什莫 1.XMPP:可扩展通讯和表示协议 2.XMPP是一种基于XML的即时通讯 3.XMPP的官方文档...

  • A Simply IM Prototype

    即时通讯也就是 IM,QQ,微信等的总称。即时通讯协议有XMPP,MQTT等。XMPP协议在 IM 上生态较为完善...

  • XMPP通讯安全--SASL协议学习

    在使用smack开发聊天功能的时候,对XMPP协议中使用TLS和SSAL协议保证通讯安全的知识进行学习和记录。文章...

  • 1、node.js实战--一个极其简单的MQTT服务器及通信

    闲言碎语 MQTT,是IBM开发的一个及时通讯协议。关于更多的MQTT协议的内容,请自行百度、google之。本文...

网友评论

      本文标题:iOS开发之-XMPP协议及时通讯

      本文链接:https://www.haomeiwen.com/subject/ebklcctx.html