如何解决由不良HTTP持久连接引起的超时问题?

7
最近我一直在解决一个HTTP超时问题。经过一个多月的调查,我相当确定它是由于不良的HTTP持久连接引起的。详情如下:
  1. 这是一个iOS应用程序。
  2. 大多数用户正在运行iOS 8。
  3. 我正在使用NSURLConnection
  4. iOS 8有一个已知的keep alive bug,但我的问题是另一个问题。更具体地说,该bug会导致NSURLErrorNetworkConnectionLost,但我的错误是NSURLErrorTimedOut。然而,我不确定我的问题是否是由iOS 8的另一个bug引起的。
  5. 我的问题行为:在使用一段时间后 - 在成功发送一些HTTP请求并接收到对应的响应之后 - 一个请求将导致NSURLErrorTimedOut,而所有随后的(与最后一个请求时间不太远以便重用持久连接)请求都将导致NSURLErrorTimedOut
  6. 一些可行的解决方法:
    1. 杀死并重新启动应用程序。
    2. 关闭iPhone上的WiFi连接以强制使用3G/4G。
    3. 打开空中模式,然后关闭它。
  7. 我的分析:从行为来看,问题似乎是由一个坏掉的持久连接引起的。所有后续请求都会继续使用此持久连接,所以都会失败并显示NSURLErrorTimedOut。从可行性解决方法可以看出,它们都能正常工作,因为它们导致了坏的持久连接被丢弃并创建了新的持久连接。

我的问题是:

  1. 还有人遇到过这个问题吗?
  2. 这是iOS 8的已知bug吗?
  3. 它是由服务器的某些不同寻常的配置引起的吗?我无法控制服务器,但我知道它们使用nginx 1.6.1,并且他们的工程师正在与我一起调查此问题。我应该向他们询问哪些信息?
  4. 是否有办法强制NSURLConnection不重用当前的持久连接,而是创建一个新的持久连接,以便在检测到问题后我可以解决此问题?

更新:

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

由于我对苹果将在iOS 9上解决这个问题的希望已经破灭,所以最终我发了一个radar:http://www.openradar.me/22770738

如果您也遇到此问题,请复制我的radar,甚至最好是,如果您拥有更可靠的可重现示例,请发出自己的radar。


@MarkusSchumann 当然,这是最平凡的代码:https://github.com/an0/MokeTest/blob/master/MokeTest/ViewController.m。 - an0
@an0 - 你可以向服务器工程师请求关闭iOS客户端的keep-alive功能,然后尝试重现问题。这样可以确认我们正在正确的方向上前进。 - Durai Amuthan.H
服务器中的KeepAliveTimeout是什么? - Durai Amuthan.H
2
@Duraiamuthan.H 我的第一反应也是将 connection 头设置为 close,但不幸的是,使用 NSMutableURLRequest不可能的 - an0
2
@Duraiamuthan.H 这不是个人问题。相当一部分用户都会遇到。我认为这也不是因为网络不好的问题,因为每次关闭和打开飞行模式都可以立即解决它。 - an0
显示剩余6条评论
3个回答

2
经过两周的研究,我可以回答第三和第四个问题:
  1. 服务器上nginx的持久连接超时设置为5秒,这不应该是问题的原因。服务器工程师发现那些超时的请求实际上是正常接收和响应的,所以更可能是客户端的问题。由于我有一个可重现代码(minimal reproducible code)来排除我的代码作为原因,因此原因应该在iOS端。
  2. 我找到的唯一方法是使用CFNetwork。更高级别的API,如NSURLConnection或NSURLSession的Connection头将被系统覆盖。

据我所知,iOS不尊重KeepAliveTimeout的值,在客户端中似乎目前没有解决方法。那么为什么不尝试忽略iOS 8浏览器的keepalive,或者尝试将超时值增加到更高的值以进行测试呢? - Durai Amuthan.H
1
我在iOS 8上遇到了完全相同的问题。在服务器上禁用保持连接似乎没有帮助。唯一解决问题的方法是在服务器上启用保持连接并将值设置为30秒或更高。这看起来像是iOS 8的一个错误,与已知的保持连接错误有关。 - evalsyrelec
@mkwon 如果你有一个持续可复现的示例项目,你应该向苹果报告。我没有这样做是因为我的示例只能偶尔重现问题,而且我无法控制服务器。 - an0
@an0有任何新发现吗? - Steffen D. Sommer

0

我也遇到了同样的问题,iOS 在服务器断开连接后仍然尝试重用该连接。

为什么CFNetwork不够用

大约两年前,我切换到 CFNetwork 来解决这个问题,但最近发现使用 CFNetwork API 无法实现 SSL pinning。因此,我考虑回到 NSURLSession。

解决方案

经过一番调查,我发现系统在NSURLSession之间不会重复使用连接,所以在一定时间内创建新的 session,应该可以解决这个问题。

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

这个 bug 在 iOS 10 中还存在吗?甚至在 iOS 11 中也是如此? - an0
@an0 不确定iOS 11是否有此问题,但iOS 10和macOS 10.13/10.12仍存在此问题。 - naituw

-1
如果你在所有的请求URL中添加时间戳,会怎么样呢?我认为这将使每个请求都变得独特,也许iOS会在每次发送请求时建立新的连接(我不确定,需要尝试一下)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接