Поведение клиента протокола 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); }
- HTTP-запрос GET для файла приходит, запрашивая диапазон байтов
- iPad 1
- HTTP-запрос GET для файла приходит, запрашивая диапазон байтов
[0; <almost the end of the file>]
- сервер отвечает, начинает потоковую передачу файла
- iPad получает кусок или два, а затем в одностороннем порядке решает прекратить прием байтов с сервера и выдает отдельный
GET
запросить следующий фрагмент файла. Например, новые границы диапазона[100, almost the end of the file]
, Видео показывается в порядке. - цикл повторяется снова с шага 2. Левая граница всегда перемещается к концу файла.
- HTTP-запрос GET для файла приходит, запрашивая диапазон байтов
Я не исследовал, как именно разрывается соединение. Возможно, iPad перестает посылать пакеты TCP ACK, полагаю, это не так уж важно.
Моя проблема в том, что за каждое разорванное соединение я получаю java.net.SocketException: Broken pipe
исключение. Это не только загрязняет журналы (что является незначительной / решаемой проблемой), но я считаю, что это может снизить производительность, так как создание исключений довольно дорого. При просмотре простого видео частота исключений составляла около 1 исключения в секунду, но если на сервере было 100 одновременных пользователей, JVM, вероятно, тратила бы кучу времени только на вычисление трассировки стека вместо выполнения реальной работы.
Я также проверил это на iPhone с использованием iOS 6 и смог наблюдать то же поведение, что и на iPad 1. Просто повторюсь, этого не происходит ни на Samsung Android, ни на любых браузерах для настольных компьютеров, которые я пробовал, в том числе Safari на настольном Mac.
Вопросы:
- это известная ошибка / особенность iPad / iPhone?
- Есть ли обходной путь для этого?
4 ответа
IIRC, "сломанный канал" просто означает, что другая сторона получила данные после того, как она завершила чтение.
Самое разумное, о чем я могу подумать, это то, что это попытка не тратить большие объемы загружаемой видео, которое никогда не просматривается (возможно, это то, о чем они договорились с операторами, что, как я подозреваю, является причиной ограничения "прямой трансляции"):
"Потоковое видео для передачи по сотовой сети более 10 минут должно использовать потоковую передачу HTTP Live и включать базовый поток HTTP Live Stream со скоростью 64 кбит / с".
Единственный простой способ ограничить загрузку - остановить read()
и дождаться заполнения окна получения, но это не всегда легко сделать (NSURLConnection
на самом деле это не так просто, например).
Если вам чрезвычайно повезло, клиент закроет конец записи (так, чтобы сервер read()
EOF) и немного подождите, прежде чем закрыть конец чтения. В этом случае может быть безопасно предположить, что клиент больше не хочет остальную часть загрузки. RFC 2616 немного мутен (кажется, что он забывает, что сокеты могут быть закрыты только в одном направлении), но упоминает "изящное закрытие" (что, по мнению Microsoft, включает в себя закрытие стороны записи и завершение чтения со стороны чтения до истечения времени ожидания), но и говорит
Серверы НЕ ДОЛЖНЫ закрывать соединение во время передачи ответа, если не подозревается сбой сети или клиента.
Так что, если вы знаете, что это iDevice и читаете EOF, сервер может безопасно закрыть сокет, при условии, что вы тщательно проверили, что он ничего не нарушает - изменение поведения HTTP в зависимости от User-Agent выглядит как ужасная идея
Как вариант, пофиг. Вы можете использовать UA сниффинг и игнорировать исключение, если это iDevice (что кажется менее страшным, чем изменение поведения HTTP). Затраты на исключение почти наверняка пренебрежимо малы и, вероятно, намного ниже, чем затраты на их печать в журнал. 100 исключений в секунду - это ничто. Профиль, если вы не уверены.
Вы также можете отправить сообщение об ошибке в Apple, но, как говорится, это не особенно сомнительное поведение сети.
Вам нужно отправить заголовок Accept Ranges при потоковой передаче на iPhone. Это может быть причиной вашей проблемы. Посмотри на этот пост
MP4 проигрывается при прямом доступе, но не при чтении через PHP на iOS
Прежде всего,
тратить кучу времени только на вычисление следов стека
не будет рассчитывать, если вы не собираетесь вызывать get/printStackTrace(). выключите журнал или поймайте его и избегайте где-нибудь.
У меня были те же проблемы, но и для меня это не прошло. Ну, это действительно глупый выбор, но вы можете использовать балансировщик нагрузки, который принимает соединения и перенаправляет их на ваш сервер tomcat или glassfish, что бы вы ни использовали. я наблюдал это отсутствие поведения сломанной трубы, когда я начал использовать ELB на AWS. NGINX или Apache могут сделать для вас пограничное общение.
Я говорю это потому, что даже операционная система может быть причиной, по которой JVM не получает надлежащего завершения связи по TCP из-за реализации JVM в ОС.
Возвращаясь к нашей общей находке. Посмотрите на это обсуждение на веб-сайте Apple. Кажется, что теперь эта проблема вызывает проблемы с iOS6, потребляющим слишком много данных во время потоковой передачи.
(На голландском языке, но здесь описана точно такая же проблема. Android выполняет 1 запрос, iOS выполняет несколько запросов)
Пришло время повторно протестировать этот материал с iOS6.0.1, чтобы увидеть, действительно ли они ИСПРАВЛЕНЫ проблемы запроса диапазона.
Только что протестировано на моем iPod Touch 5-го поколения - iOS6.0.1: запрос диапазона все еще запрашивает 0-1, затем несколько раз 0-полный файл, за которым следуют меньшие диапазоны. Однако это все еще выглядит грязно