美文网首页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