如何解决HTTP持久连接错误导致的超时问题?

时间:2015-03-06 17:54:17

标签: ios nginx nsurlconnection keep-alive persistent-connection

我最近一直在努力解决HTTP超时问题。经过一个多月的调查,我很确定它是由错误的HTTP持久连接引起的。详情如下:

  1. 这是一款iOS应用。
  2. 大多数用户都在运行iOS 8。
  3. 我正在使用NSURLConnection
  4. iOS 8有一个known keep alive bug,但我的是另一个问题。更具体地说,该错误导致NSURLErrorNetworkConnectionLost,但我的错误是NSURLErrorTimedOut。但是,我不确定我的问题是否是由iOS 8的另一个错误造成的。
  5. 我的问题的行为:经过一段时间的使用 - 在成功发送一些HTTP请求并收到相应的响应后 - 一个请求将导致NSURLErrorTimedOut,以及所有跟随(距离最后一个请求不太远)重用持久连接请求的时间会导致NSURLErrorTimedOut
  6. 一些工作方法:
    1. 杀死并重启应用。
    2. 在iPhone上关闭WiFi连接以强制使用3G / 4G。
    3. 打开空气模式然后将其关闭。
  7. 我的分析:从行为来看,这个问题似乎是由一个糟糕的持续连接引起的。所有后续请求都继续使用此持久连接,因此所有请求都以NSURLErrorTimedOut失败。从解决方法中我们可以看到所有这些都有效,因为它们会导致丢弃不良的持久连接并创建新的持久连接。
  8. 我的问题:

    1. 还有其他人遇到过这个问题吗?
    2. 这是iOS 8的已知错误吗?
    3. 是否是由服务器的一些非常规配置引起的?我不控制服务器,但我知道他们使用的是nginx 1.6.1,他们的工程师正在和我一起调查这个问题。我应该向他们索取哪些信息?
    4. 有没有办法强制NSURLConnection不重复使用当前的持久连接,而是创建一个新连接,以便在我的代码中检测到它后可以解决这个问题?

    5. 更新

      我通过使用CFNetwork并直接控制Connection标头成功地在iOS 8上缓解了此问题。然而,在iOS 9上似乎问题变得更糟。

      由于我希望Apple能够在iOS 9上修复它,我终于开了一个雷达:http://www.openradar.me/22770738

      如果你也遇到这个问题,请复制我的雷达,或者更好的是,如果你有一个更可靠的可重复样本,你可以发射你自己的雷达。

3 个答案:

答案 0 :(得分:2)

经过2周的研究,我可以回答问题3和4:

  1. nginx的持久连接超时在服务器上设置为5秒,这不应该是原因。服务器工程师发现这些超时请求实际上是正常接收和响应的。所以它更可能是客户端问题。由于我有一个minimal reproducible code来排除我的代码作为原因,因此原因应该在iOS中。
  2. 我找到的唯一方法是使用CFNetwork。更高级别的API(例如NSURLConnectionNSURLSession的{​​{1}}标头将被系统覆盖。

答案 1 :(得分:0)

同样的问题,iOS只是在服务器丢弃后尝试重用连接。

为什么CFNetwork不够

大约两年前,我转而使用CFNetwork来解决这个问题,但最近我发现用CFNetwork API实现SSL固定是不可能的。所以现在我正在考虑回到NSURLSession。

解决方法

经过一番挖掘,我发现系统将重用NSURLSession之间的连接,因此在一段时间内创建新会话应该可以解决问题。

但我也发现(至少在macOS上):NSURLSession建立的每个连接都可以持续持续180秒,并且该连接没有通过释放关闭或重置会话,因此您可能需要实现一些缓存机制避免同时创建大量连接。

以下是我目前使用的简单机制:

@interface HTTPSession : NSObject

@property (nonatomic, strong) NSURLSession * urlSession;
@property (nonatomic, assign) CFTimeInterval flushTime;

@end

+ (NSURLSession *)reuseOrCreateSession
{
    static NSMutableArray<HTTPSession *> * sessions = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sessions = [NSMutableArray<HTTPSession *> array];
    });

    const CFTimeInterval serverTimeoutSeconds = 10;
    const CFTimeInterval systemTimeoutSeconds = 40;
    CFTimeInterval now = CFAbsoluteTimeGetCurrent();

    HTTPSession * resultSession = nil;
    for (HTTPSession * session in sessions) {
        CFTimeInterval lifeTime = now - session.flushTime;
        if (lifeTime < serverTimeoutSeconds) {
            resultSession = session;
            break;
        }
        if (lifeTime > systemTimeoutSeconds) {
            resultSession = session;
            resultSession.flushTime = now;
            break;
        }
    }

    if (!resultSession) {
        resultSession = [HTTPSession new];

        NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];

        // setup session

        resultSession.urlSession = session;
        resultSession.flushTime = now;

        [sessions addObject:resultSession];
    }

    return resultSession.urlSession;
}

答案 2 :(得分:-1)

如果您为所有请求网址添加时间戳,该怎么办?我认为这会使每个请求都是唯一的,每次发送请求时iOS都会建立新的连接(我不确定。需要尝试)