【问题标题】:How to solve timeout issues caused by bad HTTP persistent connection?如何解决 HTTP 长连接错误导致的超时问题?
【发布时间】:2015-05-08 09:48:15
【问题描述】:

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

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

我的问题:

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

更新:

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

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

如果您也遇到此问题,请复制我的雷达,或者如果您有更可靠的可重现样本,则最好使用您自己的雷达。

【问题讨论】:

  • 你能分享一下设置请求和回调的代码吗?
  • @MarkusSchumann 当然,最普通的代码:github.com/an0/MokeTest/blob/master/MokeTest/ViewController.m
  • @an0 - 您可以要求服务器工程师关闭 iOS 客户端的 keep-alive 功能并尝试重现问题。只是为了确认我们的方向正确。
  • @Duraiamuthan.H 我的第一个虽然也是将connection 标头设置为close,但不幸的是它是impossibleNSMutableURLRequest
  • @Duraiamuthan.H 这不是个人问题。相当多的用户会遇到它。我认为这不是一个糟糕的互联网问题,因为每次只需关闭和打开 Air Mode 即可立即解决。

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


【解决方案1】:

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

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

【讨论】:

  • iOS 不尊重 KeepAliveTimeout 值 AFAIK 并且在客户端似乎没有办法解决它。所以为什么不尝试忽略 iOS 8 浏览器的 keepalive 或尝试将超时值增加到更高的价值只是为了测试。
  • 我在 iOS 8 上遇到了同样的问题。在服务器上禁用保持活动似乎没有帮助。解决该问题的唯一方法是在服务器上启用保持活动并将值设置为 30 秒或更高。这看起来像是 iOS 8 的一个错误,并且与已知的 keep alive 错误有关。
  • @mkwon 如果您有一个持续可重复的示例项目,您绝对应该向 Apple 报告。我没有,因为我的示例只能偶尔重现问题,而且我无法控制服务器。
  • 关于这个@an0 有什么新发现吗?
【解决方案2】:

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

为什么 CFNetwork 还不够

大约两年前,我改用 CFNetwork 来解决这个问题,但最近我发现无法使用 CFNetwork API 实现 SSL pinning。所以现在我正在考虑回到 NSURLSession。

解决方法

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

但我也发现(至少在 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;
}

【讨论】:

  • iOS 10 中还有这个 bug 吗?甚至是 iOS 11?
  • @an0 不确定 iOS 11,但 iOS 10 和 macOS 10.13/10.12 仍然存在此问题
【解决方案3】:

如果您在所有请求 URL 中添加时间戳会怎样?我认为这将使每个请求都独一无二,并且每次发送请求时 iOS 都会建立新连接(我不确定。需要尝试)

【讨论】:

    猜你喜欢
    • 2019-07-16
    • 1970-01-01
    • 1970-01-01
    • 2011-06-02
    • 2019-09-01
    • 2014-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多