美文网首页IMiOS DeveloperXmpp
iOS—XMPP重连以及其他问题(完结)

iOS—XMPP重连以及其他问题(完结)

作者: 笑谈红尘乱离人 | 来源:发表于2016-11-11 23:10 被阅读921次

在实际应用场景中,会遇到各种连接的问题,比如网络差导致socket断开连接、发了ping包服务器无响应、服务器主动剔除下线、前后端无法解析数据包等,都会影响用户的体验。

在这样的情况下,通常会设计一个重连的机制,每当长连接断开之后,按照某种方式启动重连操作,在用户无任何感知的情况下重新建立起长连接,这样才是一个不错的体验。

XMPP框架中,你并不需要自己去实现重连,框架内提供了重连的方法,这个类就是XMPPReconnect,你只需要几个步骤即可实现。

@interface XMPPManager ()

@property (nonatomic, strong) XMPPStream *xmppStream;
/** 定时发送心跳包 */
@property (nonatomic, strong) XMPPAutoPing *xmppAutoPing;
/** 重连的类 */
@property (nonatomic, strong) XMPPReconnect *xmppReconnect;

@end

@implementation XMPPManager {
    /** 重连次数,初始化为0 */
    NSInteger reconnectCount;
    /** ping超时次数,初始化为0 */
    NSInteger pingTimeoutCount;
    /** 收消息的队列,是一个串行队列 */
    dispatch_queue_t _streamQueue;
}

// 单例方法,略...
// 重新init方法,在里面调用setupStream方法,略...

 /** Setup the XMPP stream */
- (void)setupStream {
    _xmppStream = [[XMPPStream alloc] init];
    [_xmppStream addDelegate:self delegateQueue: _streamQueue];
        
    _xmppAutoPing = [[XMPPAutoPing alloc] init];
    _xmppAutoPing.pingInterval = 20.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)];
}

这样重连的类就初始化好了,接着把代理回调写上,一旦XMPP断开连接就会走回调方法。

......
#pragma mark - XMPPReconnectDelegate

- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {
    QCLog(@"🍎xmpp意外断开连接。");
}

- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {
    reconnectCount++;
    self.isReconnecting = YES;
    QCLog(@"🍎xmpp自动重连...第%@次", @(reconnectCount));

    if (reconnectCount < 5) {
    }
    else if (reconnectCount >= 5 && reconnectCount <= 10) {
        [self.xmppReconnect resSetupReconnectTimerWithTimerInterval:9.f];
    }
    else if (reconnectCount > 10 && reconnectCount <= 15) {
        [self.xmppReconnect resSetupReconnectTimerWithTimerInterval:15.f];
    }
    else {
        [self reconnectImmediately];
    }
    return YES;
}
......

- (void)reconnectImmediately {    
    self.xmppReconnect.reconnectTimerInterval = 3.f;
    reconnectCount = 0;

    [self.xmppReconnect stop];
    [self.xmppReconnect manualStart];
}

这里需要解释一下,当失去连接的时候,会立马启动重连,前5次每隔3秒重连一下,后5次每隔9秒重连一下,再后面5次就15秒重连一次,如果这15次都失败了,则再按照这个规则做重连。我没有做多少次重连失败就抛弃,这样会对服务器造成一定的压力,可以自行考虑利弊。仔细想想,这么多次都失败了,估计也连不上了吧,应该没必要再去重连了。可以做成如果10次连接都失败了,那就不再重连。等到用户主动触发某操作的时候再启动重连,或者App前后台切换的时候做重连。这些只是个人想法。
我在XMPPReconnect类里面扩展了一个方法,用于动态调整重连的时间间隔,方法如下:

- (void)resSetupReconnectTimerWithTimerInterval:(NSTimeInterval)interval
{
    // 清除计时器
    if (reconnectTimer != NULL)
    {
        if (reconnectTimer)
        {
            dispatch_source_cancel(reconnectTimer);
            reconnectTimer = NULL;
        }
    }

    if ((reconnectDelay <= 0.0) && (reconnectTimerInterval <= 0.0))
    {
        // All timed reconnect attempts are disabled
        return;
    }

    reconnectTimerInterval = interval;
    reconnectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue);

    dispatch_source_set_event_handler(reconnectTimer, ^{ @autoreleasepool {
        [self maybeAttemptReconnect];
    }});

#if !OS_OBJECT_USE_OBJC
    dispatch_source_t theReconnectTimer = reconnectTimer;

    dispatch_source_set_cancel_handler(reconnectTimer, ^{
        XMPPLogVerbose(@"dispatch_release(reconnectTimer)");
        dispatch_release(theReconnectTimer);
    });
#endif

    dispatch_time_t startTime;
    if (reconnectDelay > 0.0) {
        startTime = dispatch_time(DISPATCH_TIME_NOW, (reconnectDelay * NSEC_PER_SEC));
    } else {
        startTime = dispatch_time(DISPATCH_TIME_NOW, (reconnectTimerInterval * NSEC_PER_SEC));
    }

    uint64_t intervalTime;
    if (reconnectTimerInterval > 0.0) {
        intervalTime = reconnectTimerInterval * NSEC_PER_SEC;
    } else {
        intervalTime = 0.0;
    }

    dispatch_source_set_timer(reconnectTimer, startTime, intervalTime, 0.25);
    dispatch_resume(reconnectTimer);
}

关于ping,我这边的做法是:客户端每隔20s发送ping包,当客户端发送的两次ping包给服务器后都没收到服务器返回的pong包,就手动断开连接(会立刻进入重连)。

......
#pragma mark - XMPPAutoPingDelegate

- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing *)sender{    
    // 如果至少有1次超时了,再收到ping包,则清除超时次数
    if (pingTimeoutCount > 0) {
        pingTimeoutCount = 0;
    }
}

- (void)xmppAutoPingDidTimeout:(XMPPAutoPing *)sender {    
    // 收到两次超时,就disconnect吧
    pingTimeoutCount++;
    if (pingTimeoutCount >= 2) {
        [self.xmppStream disconnect];
    }
}
......

关于多个设备登录,也是一个网友问到的问题,当在一个设备上某账号XMPP登录了之后,再在一台设备上登录此账号,则原先的账号会收到服务器发来的一条错误消息,当然,前提是先写上回调。可以按照如下的方法来确定是否被抢登了:

- (void)xmppStream:(XMPPStream *)sender didReceiveError:(NSXMLElement *)error {
    // <stream:error xmlns:stream="http://etherx.jabber.org/streams"><conflict xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>
    NSString *conflict = [[error elementForName:@"conflict"] stringValue];
    if (conflict) {
        // 这里被挤下线了,做点儿什么吧....
        // 一般会提示用户,账号退出登录,回到登录页面
    }
}

关于XMPP的调试日志,当要调试收发消息的时候,没有log是一件让人很郁闷的事情,正确的打印log非常有利于解决各种疑难杂症。还记得吗,在这篇文章中就提到了XMPP框架引用了三个开源库,其中一个就是CocoaLumberjack,这个就是我们所要用到的调试利器,屡试不爽。现在就来看看怎么使用它吧。(这个日志的使用也折磨了老久......)
首先在XMPPManager类的.m文件的头顶上写上:

#import "DDLog.h"
#import "DDTTYLogger.h"
#import "DDFileLogger.h"    

#ifndef QCConsoleLoggingEnabled
// 1开启,0关闭
#define QCConsoleLoggingEnabled 0
#endif

#define QCContext           102333
#define QCLogAsync          YES

#if QCConsoleLoggingEnabled

#define QCLogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(QCLogAsync, logLevelQC, flg, QCContext, frmt, ##__VA_ARGS__)
#define QCLogTrace()              QCLogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)

#ifndef QCLogLevelll
#define QCLogLevelll LOG_LEVEL_VERBOSE
#endif
// Log levels : off, error, warn, info, verbose
static const int logLevelQC = QCLogLevelll;

#else

#define QCLogObjc(flg, frmt, ...) {}
#define QCLogTrace() {}

#endif

再增加一个方法,用来控制是否要打印log,在初始化XMPPManager的时候调用即可:

- (void)addLogs {
#if QCConsoleLoggingEnabled
    // 控制台log
    DDTTYLogger *logger = [DDTTYLogger sharedInstance];
    [DDLog addLogger:logger withLevel:DDLogLevelAll];
#endif
    // 文件log,会保存在沙盒里面
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; 
    fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    [DDLog addLogger:fileLogger];
}

关于后台运行?
关于消息回执?(其实可以简单的说说的......)
关于消息重发?抱歉,我并没有做,因为已经来不及了,其实之前我有很多时间去做,只是……算了不说。

XMPP,爱过……恨过……

相关文章

网友评论

  • 程序狗旭旭旭:你好,问个问题。在XMPPReconnect类里面扩展的方法里有个 [self maybeAttemptReconnect]; 我再xmppframework里看到 这个方法是放在@interface XMPPReconnect (PrivateAPI)这里的 调用不到。你是怎么解决的呢?
    笑谈红尘乱离人:如果用CocoaPods管理的XMPP库,那就只能用runtime了;手动添加则直接改源码就行,把maybeAttemptReconnect这个方法直接提到实现头文件去也行。
    笑谈红尘乱离人:在 XMPPReconnect 类的头文件里声明一个方法,在实现文件里调用 [self maybeAttemptReconnect]; 就可以啦,都不需要用到runtime
    程序狗旭旭旭:解决了楼主。我用的runtime。谢谢分享~

本文标题:iOS—XMPP重连以及其他问题(完结)

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