【问题标题】:Sending SPDY requests results in "The request timed out" errors with NSUrlSession in iOS在 iOS 中使用 NSUrlSession 发送 SPDY 请求会导致“请求超时”错误
【发布时间】:2015-12-06 05:40:37
【问题描述】:

我的 iOS 应用从 nginx HTTP 服务器加载图像。在我发送 400 多个此类请求后,网络“卡住”并且所有后续 HTTP 请求都会导致“请求超时”错误。只有重新启动应用程序才能重新加载图像。

详情:

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

有趣的是:

  1. 我只能在 HTTPS 请求和服务器上启用 SPDY 时遇到此问题。
  2. 非安全 HTTP 请求工作正常。
  3. 非 SPDY HTTPS 请求工作正常。我通过在 nginx 配置中关闭服务器端的 SPDY 来测试它。
  4. 问题出现在 iOS 8 和 9、物理设备和模拟器上。支持 Wi-Fi 和 LTE。
  5. 当我查看 nginx 访问日志时,我仍然可以看到“卡住”的请求进入。重要的细微差别:请求日志记录出现在 iOS 应用程序在超时期限结束后放弃它的确切时刻.
  6. 我希望用Charles Proxy 分析HTTP 请求,但是当请求通过Charles 时问题会自行解决。也就是说 - 一切都适用于查尔斯,就像量子力学中的效果一样,当观察的事实影响结果时。
  7. 当 iOS 应用程序连接到两个具有完全不同 nginx 配置的不同服务器时,我能够重现该问题。这可能意味着该问题与特定的 nginx 设置无关。
  8. 我使用“活动监视器”工具分析了应用程序。它在批量 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 ...

【问题讨论】:

  • nginx 对这些请求做了什么?显示配置
  • @AlexeyTen,这里是 nginx 配置:pastebin.com/pYYjdxfP
  • 发生这种情况时,客户端上运行了多少线程?
  • @quellish 只有一个线程。 iOS 应用按顺序发送请求,一个接一个,间隔为 10 毫秒。
  • 如果您可以重现它,请这样做并在调试器中停止应用程序 - 查看有多少线程正在运行。很可能所有这些请求都在堆积起来,等待资源被释放,然后才能继续 - 网络延迟、线程等系统资源等。这也可以解释为什么在使用 Charles 时它不会重现。

标签: ios swift nginx nsurlsession


【解决方案1】:

一种可能是 iOS 开始发送请求,然后丢包导致标头和请求正文无法完全传递。

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

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

您的应用程序是否会因为过多的异步调度到新队列而导致大量线程?因为这很容易导致各种奇怪的不当行为。

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

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

【讨论】:

  • 感谢您的帮助。 iOS 应用按顺序发送请求,一个接一个,间隔为 10 毫秒。如果之前的请求处于“正在运行”状态,则取消之前的请求。
  • 我会尝试在没有 HTTPS 的情况下重现问题,好点。
  • 哇。这是一个非常短的超时。您可能超出了会话可以自行清理的速度。您是否使用 NSURLConnection 尝试过,如果是,是否会以同样的方式失败? (并不是说我建议通常使用已弃用的 API,但知道它们在这方面的行为是否不同会很有趣。)
  • 是的,这是一个非常短的间隔,我特意把它设置得这么短,以便我可以在演示应用程序中可靠地重现它,以便 Apple 可以对其进行调试。在我的真实应用程序中,请求之间的间隔永远不会短于 100 毫秒,但即使使用这个更大的时间间隔,我也会遇到同样的问题。
  • 听起来这可能是一个 SPDY 错误,无论是在 iOS 还是 nginx 中。绝对提交错误并附加该测试工具。我相信 CFNetwork 团队很想看看它。
猜你喜欢
  • 1970-01-01
  • 2018-01-02
  • 2014-03-16
  • 2016-05-27
  • 1970-01-01
  • 2013-10-06
  • 2021-01-14
  • 2018-01-03
  • 2021-03-31
相关资源
最近更新 更多