发送 SPDY 请求导致 "The request timed out" 错误 iOS 中的 NSUrlSession

Sending SPDY requests results in "The request timed out" errors with NSUrlSession in iOS

我的 iOS 应用程序从 nginx HTTP 服务器加载图像。在我发送 400 多个这样的请求后,网络 'gets stuck' 和所有后续 HTTP 请求导致 "The request timed out" 错误。我只有在重新启动应用程序时才能使图像再次加载。

详情:

  1. 我正在使用 NSURLSession.sharedSession().dataTaskWithURL 向 jpeg 文件发送四百个 HTTP GET 请求。
  2. 请求按顺序发送,一个接一个。请求之间的间隔是10毫秒。
  3. 使用 NSURLSessionDataTask 对象的 cancel() 方法取消每个先前未完成的请求。

有趣的是:

  1. 我只能在 HTTPS 请求和服务器上启用 SPDY 时遇到此问题。
  2. 非安全 HTTP 请求工作正常。
  3. 非 SPDY HTTPS 请求工作正常。我通过在服务器端关闭 SPDY 来测试它,在 nginx 配置中。
  4. 问题同时出现在 iOS 8 和 9、物理设备和模拟器中。支持 Wi-Fi 和 LTE。
  5. 当我查看 nginx 访问日志时,我仍然可以看到 'stuck' 请求进入。重要的细微差别:请求日志记录出现在 iOS 应用程序放弃的确切时刻在超时期限结束后。
  6. 我希望用 Charles Proxy 分析 HTTP 请求,但当请求通过 Charles 时问题自行解决。也就是说 - 一切都适用于 Charles,就像量子力学中观察的事实影响结果时的效果。
  7. 当 iOS 应用程序连接到两个具有截然不同的 nginx 配置的服务器时,我能够重现该问题。这可能意味着该问题与特定的 nginx 设置无关。
  8. 我使用 "Activity Monitor" 仪器分析了应用程序。它在批量 HTTP 请求期间使用的线程数从 5 跳到 10。相比之下,当我只发送一个 HTTP 请求时,线程数跳到 8。CPU 负载很少超过 30%。

问题的原因可能是什么?谁能推荐其他分析调试的方法或工具?

用调度工具分析

演示应用程序

这个演示应用程序为我重现了 100% 的问题。

https://github.com/exchangegroup/ImageLoadDemo

版本和设置

我的 nginx 配置:http://pastebin.com/pYYjdxfP

OS X: 10.10.4 (14E46), iOS: 8 和 9, Xcode: 7.0 (7A218), nginx: 1.9.4

不理想的解决方法

仅当我为每个单独的请求创建一个新的 NSURLSession 并使用 finishTasksAndInvalidateinvalidateAndCancel.

清除之前的会话时,我才设法使请求保持工作状态
// Request 1

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()     
let session = NSURLSession(configuration: configuration)
session.dataTaskWithURL ...

// Request 2

// clear the previous request
session.finishTasksAndInvalidate()
let session2 = NSURLSession(configuration: configuration)
session2.dataTaskWithURL ...

一种可能是 iOS 开始发送请求,然后数据包丢失导致 headers 和请求 body 无法完全传送。

我想到的另一种可能性是,您的服务器可能在实际完成尝试传递请求之前不会记录请求,这将使服务器日志中的时间戳与连接关闭的时间对齐,而不是比打开时。 (IIRC,这就是 Apache 所做的;我没有使用过 nginx,所以我不能代表它的行为。)如果是这样,那么这只是一个简单的连接停顿。至于为什么会卡顿,我猜不出来。

问题是否专门针对 HTTPS 流量出现?如果可以用 HTTP 重现,就不需要 Charles Proxy;只需使用 OS X 的 "Internet Sharing" 功能,并使用 tcpdump 或 wireshark 捕获数据包,监听桥接口。如果您不能使用 HTTP 重现它,我的钱将用于获取 CRL 或在验证服务器证书时执行 OCSP 检查的问题。

您的应用程序是否由于过度异步分派到新 queues 而导致大量线程?因为这很容易导致各种奇怪的不当行为。

超时时间是多久?如果它太短,您的应用程序可能 运行 在处理仅四秒内交付的 400 个请求的结果时遇到硬件的性能限制。

此外,您是否尝试同时安排这些请求?因为我似乎记得读过一个错误,如果您同时在单个 session 中启动太多任务,该错误会导致 NSURLSession 撞墙。您可以尝试仅在 session 中的任务数量低于某个阈值后才添加任务,看看是否能解决问题。