背景
项目是基于MQTT,但重连逻辑被重写过。项目中会先请求LBS接口返回ip地址,再进行socket链接。
需求
之前是只有一个LBS地址,失败之后内部重试链接。现在服务端会返回两个ip地址,第一个失败之后再进行第二个连接。
socket连接逻辑
- (void)open {
self.state = DOLPTransportOpening;
NSError* connectError;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)self.host, self.port, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
...
if (!connectError) {
self.encoder = [[DOLPSSLSecurityPolicyEncoder alloc] init];
CFWriteStreamSetDispatchQueue(writeStream, self.queue);
self.encoder.stream = CFBridgingRelease(writeStream);
self.encoder.securityPolicy = self.tls ? self.securityPolicy : nil;
self.encoder.securityDomain = self.tls ? self.host : nil;
self.encoder.delegate = self;
if (self.voip) {
[self.encoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.encoder open];
self.decoder = [[DOLPSSLSecurityPolicyDecoder alloc] init];
CFReadStreamSetDispatchQueue(readStream, self.queue);
self.decoder.stream = CFBridgingRelease(readStream);
self.decoder.securityPolicy = self.tls ? self.securityPolicy : nil;
self.decoder.securityDomain = self.tls ? self.host : nil;
self.decoder.delegate = self;
if (self.voip) {
[self.decoder.stream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
}
[self.decoder open];
} else {
[self close];
}
}
socket连接使用CFStreamCreatePairWithSocketToHost,超时由系统控制。
socket连接坏的IP地址,超时时间在75s,无法控制超时时间。
解决方案
在[self.encoder open];时dispatch_after控制超时时间,如果在规定时间内没有回调,则认为ip连接失败。返回给上层,进行下一个ip连接。
改造后引起新问题
源码阅读参考MQTT源码阅读及问题分析
偶现socket未连接,复现概率比较低。只有结合日志,阅读源码。
- (void)createClient:(NSString *)host port:(int32_t)port callback:(void (^)(BOOL, NSError *))callback {
NSString *bundlePath = [[NSBundle bundleForClass:[self class]].resourcePath stringByAppendingPathComponent:@"/DOLPCertificate.bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
NSString *caPath = [bundle pathForResource:@"ca_chain" ofType:@"der"];
// 设置tls认证
DOLPSSLSecurityPolicy *securityPolicy = [DOLPSSLSecurityPolicy policyWithPinningMode:SSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
securityPolicy.validatesCertificateChain = NO;
securityPolicy.pinnedCertificates = @[[NSData dataWithContentsOfFile:caPath]];
DolpBaseData *connData = [DolpProtoDataUtil createWithConnection:self.token deviceId:self.deviceId];
NSString *clientId = [DolpDeviceUtil deviceId];
self.sessionManager = [[DOLPSessionManager alloc] initWithQueue:self.dolpCodecQueue];
[self.sessionManager connectTo:host port:port tls:YES keepalive:DOLP_KEEP_ALIVE clean:YES payload:[connData data] withClientId:clientId securityPolicy:securityPolicy certificates:nil protocolLevel:DOLPProtocolVersion10 connectHandler:^(NSError *error) {
if (error) {
callback(false, error);
BLLogError(@"【socket】服务器连接失败 %@ | host:%@ | port:%@", error,host,@(port));
} else {
callback(true, nil);
BLLogInfo(@"【socket】服务器连接成功host:%@ | port:%@",host,@(port));
}
}];
self.sessionManager.delegate = self;
}
突破点在于connectHandler这个block;为什么没有回调。
解决问题
- 经过日志排查,源码阅读;排查发现mqtt在进前台时,也进行了session重连,把lbs的block吞掉了。只有当mqtt的进前台的连接在lbs后面才会发生。
//ForegroundReconnection
- (void)appDidBecomeActive {
[self.sessionManager connectToLast:nil];
[self endBackgroundTask];
}
- 修改:依靠lbs接口逻辑进行重连,进前台不走mqtt的重连,统一调用。
a. 仔细分析发现:以前就一个ip地址,重连也是原来的ip。
b. 现在可能重连是坏的ip地址。
小结
- 由于mqtt源码在接手之前就被改过,不确定改了哪些代码。然后对mqtt的源码不熟悉,排查问题产生一定的难度。对于第三方库,如果要修改,建议使用runtime方式,后来开发者能知道修改的代码。
- 经过不断测试,尝试寻找复现路径;结合日志,阅读源码,看看为什么被吞掉了。代码分析花了两天半,解决问题花了两小时。