在实际应用场景中,会遇到各种连接的问题,比如网络差导致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,爱过……恨过……
网友评论