socket偶现未连接

时间:2021-7-9 作者:qvyue

背景

项目是基于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;为什么没有回调。

解决问题

  1. 经过日志排查,源码阅读;排查发现mqtt在进前台时,也进行了session重连,把lbs的block吞掉了。只有当mqtt的进前台的连接在lbs后面才会发生。
//ForegroundReconnection
- (void)appDidBecomeActive {
    [self.sessionManager connectToLast:nil];
    [self endBackgroundTask];
}
  1. 修改:依靠lbs接口逻辑进行重连,进前台不走mqtt的重连,统一调用。
    a. 仔细分析发现:以前就一个ip地址,重连也是原来的ip。
    b. 现在可能重连是坏的ip地址。

小结

  1. 由于mqtt源码在接手之前就被改过,不确定改了哪些代码。然后对mqtt的源码不熟悉,排查问题产生一定的难度。对于第三方库,如果要修改,建议使用runtime方式,后来开发者能知道修改的代码。
  2. 经过不断测试,尝试寻找复现路径;结合日志,阅读源码,看看为什么被吞掉了。代码分析花了两天半,解决问题花了两小时。
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。