【问题标题】:HTTP byte range protocol client behaviour on iPad/iPhoneiPad/iPhone 上的 HTTP 字节范围协议客户端行为
【发布时间】:2012-09-28 09:55:39
【问题描述】:

我正在测试支持HTTP byte range requestsHTTP servlet implementation (kindly shared by BalusC)

我发现不同 HTTP 客户端之间存在一些特殊差异,我想知道我是否没有遗漏任何内容。我在测试中使用了 >2G 的 mp4 视频文件,并且正在使用 Wireshark 捕获数据包。大致是这样的:

  • 三星 Galaxy SII:

    • 对文件的HTTP GET请求来了,要求字节范围[0; <almost the end of the file>]
    • 服务器响应,开始流式传输文件
    • 每个后续块都在相同的 HTTP 响应的范围内提供服务。不会发送新的 HTTP 请求(除非视频被快速转发到某个位置)。流式代码块非常简单,它读取RandomAccessFile input并通过byte[] buffer写入OutputStream output

      while ((read = input.read(buffer)) > 0) {
          output.write(buffer, 0, read);
      }
      
  • iPad 1
    • 对文件的HTTP GET请求来了,要求字节范围[0; <almost the end of the file>]
    • 服务器响应,开始流式传输文件
    • iPad 获得一两个块,然后单方面决定停止从服务器接受字节,并为文件的下一个块发出 单独的GET 请求。新的范围边界是例如[100, almost the end of the file]。视频显示正常。
    • 循环从第 2 步开始再次重复。左边界始终向文件末尾移动。

我没有调查连接到底是如何终止的。可能是 iPad 停止发送 TCP ACK 数据包,我想这并不重要。

我的问题是,对于每个终止的连接,我都会收到 java.net.SocketException: Broken pipe 异常。这不仅会污染日志(这是一个小问题/可解决的问题),而且我相信这会损害性能,因为引发异常非常昂贵。观看简单视频时,异常率约为 1 个异常/秒,但如果服务器有 100 个并发用户,那么 JVM 可能会花费大量时间来计算堆栈跟踪而不是实际工作。

我还在使用 iOS 6 的 iPhone 上对此进行了测试,并且能够观察到与 iPad 1 相同的行为。重申一下,这不会发生在三星 Android 或我尝试过的任何桌面浏览器上,包括桌面 Mac 上的 Safari。

问题:

  • 这是 iPad/iPhone 的已知错误/功能吗?
  • 有解决办法吗?

【问题讨论】:

标签: java iphone ipad http streaming


【解决方案1】:

IIRC,“断管”只是表示对方在关闭读取端后收到数据。

我能想到的最合理的事情是,它试图不浪费大量带宽下载从未被观看过的视频(也许他们已经与运营商达成一致,我怀疑这是 "live streaming" restriction 背后的原因:

“通过蜂窝网络传输超过 10 分钟的视频流内容必须使用 HTTP Live Streaming 并包含基线 64 kbps 纯音频 HTTP Live 流。”

限制下载的唯一其他简单方法是停止 read()ing 并等待接收窗口填满,但这并不总是容易做到(例如,NSURLConnection 真的不会让这变得容易)。

如果你非常幸运,客户端会关闭它的写端(这样服务器就会read()EOF)并等待一会儿,然后再关闭它的读端。在这种情况下,可能可以安全地假设客户端不再需要下载的其余部分。 RFC 2616 有点模糊(似乎忘记了只能在一个方向上关闭套接字),但提到了“优雅关闭”(according to Microsoft 涉及关闭写入端并从读取端完成读取,直到超时通过) ,但也说

服务器不应在传输响应的过程中关闭连接,除非怀疑网络或客户端故障。

因此,如果您知道它是一个 iDevice 并且您读取了 EOF,那么服务器关闭套接字可能是安全的,前提是您已经彻底测试它不会破坏任何东西——根据 User-Agent 改变 HTTP 行为似乎是个糟糕的主意。

或者,不要在意。您可以进行 U-A 嗅探,如果它是 iDevice 则忽略异常(这似乎不如更改 HTTP 行为可怕)。异常开销几乎可以肯定可以忽略不计,并且可能远低于将其打印到日志的开销。每秒 100 个异常不算什么。如果您不确定,请对其进行概要分析。

你也可以file a bug with Apple,但随着这些事情的发展,它不是particularly dubious network behaviour

【讨论】:

  • 我不同意你关于“浪费大量带宽”的观点;客户端可以简单地延迟 TCP ACK 以便稍后获得下一个块。至少它可以做的是优雅地关闭连接。无论如何,感谢您的回答 - 这是我所拥有的最好的。我给了你积分,但如果有人对 iPad/iPhone 行为被破坏的原因提供更好的解释(如果是的话),我会继续提问。
  • @mindas 客户端可以延迟 ACK,但服务器仍会继续发送数据包,直到发送窗口填满,再说一次,这对于 @987654329 来说并不容易完成@。再一次,客户端可能尝试“优雅关闭”但超时;没有更多信息很难说。
【解决方案2】:

在 iPhone 上进行流式传输时,您需要发送 Accept Ranges 标头。这可能会导致您的问题。看看这个帖子

MP4 plays when accessed directly, but not when read through PHP, on iOS

【讨论】:

  • 感谢您的回答。事实上,我正在发送Accept-Ranges: bytes 标头,否则视频根本无法播放。任意流定位也可以,我唯一的问题是终止连接。
  • 不知道,我使用了该链接中的解决方案并且它有效。 Connection Keep-Alive 标头呢?只是谷歌搜索,它看起来像一个可能与iOS无关的常见问题:mikeschubert.com/2006/08/03/javanetsocketex
  • 解决方案有效,但我想了解为什么 iPad/iPhone 的行为与 Android 和台式机不同?如果这是一个数据库问题,它也会在其他地方失败,但现在它只在 Apple 移动设备上失败。
  • iOS 是否有可能像大多数浏览器一样不允许连接保持活动状态?我会遵循这条调查路线。或者添加一些代码来强制连接保持活动状态,或者在请求下一个块之前检查它是否仍然打开。
【解决方案3】:

首先,

花费大量时间计算堆栈跟踪

如果您不打算调用 get/printStackTrace(),则不会计算。把它关掉或抓住它并避开它。

我也有同样的问题,但我也没有消失。好吧,这些确实是愚蠢的选择,但是您可以使用负载平衡器来接受连接并将其重定向到您的服务器 tomcat 或 glassfish,无论您使用什么。当我开始在 AWS 上使用 ELB 时,我观察到了这种缺乏断管行为。 NGINX 或 Apache 可以为您做一些前沿通信。

我这么说是因为即使是操作系统也可能是 JVM 没有收到正确的 TCP 通信关闭的原因,因为操作系统上的 JVM 实现。

【讨论】:

  • 虽然它可能不会计算符号,但需要在覆盖堆栈之前保存计算堆栈跟踪所需的信息;我已经看到代码覆盖 Throwable.fillInStackTrace() 以避免这种开销(代码最终被重写为不在图像的每一行都抛出异常)。
【解决方案4】:

回到我们共同的发现。看看苹果网站上的this discussion。现在看来,这个问题已经导致 iOS6 在流式传输时消耗过多数据的问题。

(使用荷兰语但完全相同的问题报告了here。Android 执行 1 个请求,iOS 执行多个请求)

是时候用 iOS6.0.1 重新测试这些东西,看看它们是否确实修复了范围请求问题。

刚刚在我的 iPod Touch 第 5 代 - iOS6.0.1 上进行了测试:范围请求仍在请求 0-1,然后是几次 0-full 文件,然后是更小的范围。但是看起来还是很乱

【讨论】:

  • 感谢分享。据我了解,他们似乎已经承认这是其中一个应用程序(播客?)而不是浏览器的问题。无论如何,如果您发现任何问题,请随时通知我们。我已经放弃了这个问题;-(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多