Подтвердить что ты не робот

Поведение клиента протокола протокола байтов HTTP на iPad/iPhone

Я тестировал реализацию сервлета HTTP (любезно предоставленный BalusC), который поддерживает HTTP-байтовые запросы.

Я обнаружил некоторые особые отличия между разными HTTP-клиентами и задавался вопросом, не пропал ли я что-нибудь. Я использовал видеофайл > 2G mp4 для своих тестов и собирал пакеты с Wireshark. Это примерно то, что происходит:

  • Samsung Galaxy SII:

    • Запрос HTTP GET для файла поступает с запросом диапазона байтов [0; <almost the end of the file>]
    • сервер отвечает, начинает поток файла
    • каждый последующий фрагмент обслуживается в пределах того же HTTP-ответа. Новый HTTP-запрос не отправляется (если видео не переадресовано в определенную позицию). Для этого поток потокового кода довольно прост, он читает RandomAccessFile input и записывается в OutputStream output через byte[] buffer:

      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, вероятно, потратила бы массу времени, просто вычисляя трассировки стека вместо реальной работы.

Я также тестировал это на iPhone с помощью iOS 6 и смог наблюдать за тем же поведением, что и iPad 1. Чтобы повторить, этот не происходит на Samsung Android, ни на любых браузерах для настольных компьютеров, которые я пробовал, включая Safari на рабочем столе Mac.

Вопросы:

  • является ли это ошибкой/особенностью iPad/iPhone?
  • Есть ли способ для этого?
4b9b3361

Ответ 1

IIRC, "сломанная труба" просто означает, что другая сторона получила данные после того, как она закрыла свой конец.

Самое разумное, о чем я могу думать, это попытка не тратить большие объемы полосы пропускания, загружая видео, которое никогда не просматривается (возможно, то, что они согласились с носителями, которые, как я подозреваю, являются аргументами за "потоковое потоковое" ограничение:

"Потоковое потоковое видео в сети сотовой сети продолжительностью более 10 минут должно использовать потоковое HTTP-потоковое вещание и включать базовый поток HTTP-потокового аудио в формате 64 кбит/с".

Единственный простой способ дросселировать загрузку - остановить read() ing и ждать, пока окно приема заполнится, но это не всегда легко сделать (NSURLConnection действительно не делает это проще, например).

Если вам очень повезло, клиент закроет конец записи (такой, что сервер будет read() EOF), и подождите немного, прежде чем закрыть его конец для чтения. В этом случае можно с уверенностью предположить, что клиент больше не хочет остальной загрузки. RFC 2616 немного туман (кажется, забывают, что сокеты могут быть закрыты только в одном направлении), но упоминает "изящное закрытие" (которое в соответствии с Microsoft подразумевает закрытие записи и завершение чтения со стороны чтения до истечения тайм-аута), но также говорит

Серверы НЕ ДОЛЖНЫ закрыть соединение в середине передачи ответа, если не подозревают о сбое сети или клиента.

Итак, если вы знаете, что это iDevice, и вы читаете EOF, тогда для сервера может быть безопасно закрыть сокет, если вы тщательно протестировали его, что он ничего не сломал — изменение поведения HTTP в зависимости от User-Agent просто кажется ужасной идеей.

В качестве альтернативы, все равно. Вы можете использовать U-A sniffing и игнорировать исключение, если это iDevice (что кажется менее страшным, чем изменение поведения HTTP). Исключительные накладные расходы почти наверняка незначительны и, вероятно, намного ниже, чем накладные расходы на печать в журнале. 100 исключений - ничто. Профилируйте его, если вы не уверены.

Вы также можете записать ошибку с Apple, но, поскольку эти вещи идут, это не особенно сомнительное поведение сети.

Ответ 3

Прежде всего,

тратить много времени, просто вычисляя трассировки стека

не будет рассчитываться, если вы не собираетесь называть get/printStackTrace(). отключайте его от журнала или ловите его и избегайте его где-то.

У меня были те же проблемы, это тоже не ушло. Ну, это действительно глупый выбор, но вы можете использовать балансировщик нагрузки, который принимает соединения и перенаправляет его на ваш сервер tomcat или glassfish, что бы вы ни использовали. Я заметил, что это отсутствие нарушения работы, когда я начал использовать ELB на AWS. NGINX или Apache могли бы выполнить некоторые пограничные коммуникации для вас.

Я говорю об этом, потому что даже операционная система может быть причиной того, что JVM не получает надлежащее выключение TCP-связи из-за реализации JVM в ОС.

Ответ 4

Возвращаясь к нашему совместному выводу. Взгляните на эту дискуссию на веб-сайте Apple. Похоже, что теперь эта проблема вызывает проблемы с iOS6, потребляющим слишком много данных во время потоковой передачи.

(На голландском языке, но точно такая же проблема сообщила здесь. Android делает 1 запрос, iOS выполняет несколько запросов)

Время для повторной проверки этого материала с помощью iOS6.0.1, чтобы убедиться, действительно ли они исправили проблемы с запросом диапазона.

Только что протестирован на моем iPod Touch 5th Gen - iOS6.0.1: запрос диапазона по-прежнему запрашивает 0-1, затем несколько раз 0-полный файл, за которым следуют меньшие диапазоны. Однако он все еще выглядит грязным