Сокеты HTTP против ванильных для загрузки большого двоичного файла (50-200 МБ) с устройства Android через сеть Wi-Fi с одним переходом

Существуют ли значительные накладные расходы при использовании HTTP через простые сокеты (Java на Android) для отправки большого (50-200 МБ) файла [файл находится на SD-карте] с устройства Android на сервер Linux по сети Wi-Fi.

В моем текущем прототипе я использую CherryPy-3.2.0 для реализации своего HTTP-сервера. Я использую Android 2.3.3 на Nexus в качестве клиента.

В настоящее время загрузка двоичного файла размером 50 МБ занимает около ~100 секунд** (в более медленной сети 18 Мбит/с*) и ~50 секунд (в более быстрой сети 54 Мбит/с*) в сети Wi-Fi.

ПРИМЕЧАНИЕ.
*Я использую WifiInfo.getLinkSpeed() для измерения скорости сетевого соединения

** Это разница во времени до и после HTTPClient.execute(postRequest)

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

Спасибо.

РЕДАКТИРОВАТЬ - почтовый индекс HTTP на Android

private void doHttpPost(String fileName) throws Exception{

    HttpParams httpParameters = new BasicHttpParams();

    // Set the timeout in milliseconds until a connection is established.
    int timeoutConnection = 9000000;
    HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
    // Set the default socket timeout (SO_TIMEOUT) 
    // in milliseconds which is the timeout for waiting for data.
    int timeoutSocket = 9000000;
    HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

    HttpClient client = new DefaultHttpClient(httpParameters);

    client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2109);

    HttpPost postRequest = new HttpPost();
    postRequest.setURI(new URI("http://192.168.1.107:9999/upload/"));

    MultipartEntity multiPartEntity = new MultipartEntity();
    multiPartEntity.addPart("myFile", new FileBody(new File(fileName)));
    postRequest.setEntity(multiPartEntity);

    long before = TrafficStats.getTotalTxBytes();
    long start = System.currentTimeMillis();
    HttpResponse response = client.execute(postRequest);
    long end = System.currentTimeMillis(); 
    long after = TrafficStats.getTotalTxBytes();

    Log.d(LOG_TAG, "HTTP Post Execution took " + (end - start) + " ms.");


    if( before != TrafficStats.UNSUPPORTED && after != TrafficStats.UNSUPPORTED)
        Log.d(LOG_TAG, (after-before) + " bytes transmitted to the server");
    else
        Log.d(LOG_TAG, "This device doesnot support Network Traffic Stats");

    HttpEntity responseEntity = response.getEntity();


    if (responseEntity != null) {
        responseEntity.consumeContent();
        Log.d(LOG_TAG, "HTTP Post Response " + response.getEntity().getContent().toString() );
    }

    client.getConnectionManager().shutdown(); 

}

РЕДАКТИРОВАТЬ 2: На основании результатов, представленных этим инструментом, похоже, что скорость чтения SD-карты не является проблемой. Так что это может быть библиотека HttpClient или что-то еще. введите здесь описание изображениявведите здесь описание изображения


person Soumya Simanta    schedule 05.04.2011    source источник
comment
Как вы отправляете файл по HTTP? Если вы кодируете его в BASE64, это также займет много времени.   -  person jakebasile    schedule 05.04.2011
comment
@Jake Я добавил код для отправки HTTP-запроса.   -  person Soumya Simanta    schedule 05.04.2011
comment
Похоже, вы используете многокомпонентное кодирование, это хорошо. Хотите поделиться, какой тип данных вы отправляете (текст, изображение, видео и т. д.)?   -  person skabbes    schedule 05.04.2011
comment
@skabbes — это часть двоичного файла виртуальной машины (.vdi), который уже сжат и зашифрован.   -  person Soumya Simanta    schedule 05.04.2011
comment
Возможно, вы могли бы загрузить файл с сервера на себя. Это даст вам узкое место вашего сервера (независимо от пропускной способности).   -  person skabbes    schedule 05.04.2011
comment
@skabbes - отличная идея. Я собираюсь попробовать это прямо сейчас. Я сделал несколько приблизительных измерений, загрузив один и тот же файл, используя два разных браузера (Safari и Chrome на OS X) в одной и той же сети Wi-Fi (и почти в том же физическом местоположении, что и телефон Android). Обе загрузки браузера заняли около 40-45 секунд, тогда как загрузка телефона по-прежнему занимает около 90 секунд.   -  person Soumya Simanta    schedule 05.04.2011
comment
@skabbes Я загрузил тот же файл с локальной машины на себя, и это заняло около 8 секунд. Я попытался сделать это через проводную сеть, и это заняло около 10 секунд.   -  person Soumya Simanta    schedule 05.04.2011
comment
Что ж, похоже, сервер не является узким местом (неудивительно). Вы можете попытаться получить более точную оценку скорости SD-карты, загрузив с нее файл (возможно, через adb). Если это также быстрее, похоже, виновата ваша беспроводная сеть.   -  person skabbes    schedule 06.04.2011
comment
@skabbes - я попытался использовать adb pull, что дало мне следующий результат. 1576 КБ/с (48316794 байта за 29,938 с). Я также написал автономную программу на Java, используя клиентские JAR-файлы Apache HTTP (тот же самый, который использует Android) и отправил файл на сервер, работающий на локальном хосте. Вся почтовая операция заняла 6204 мс. Еще я попробовал переместить телефон ближе к точке доступа Wi-Fi. Сделав это, я увидел улучшение примерно на 30%. Так что я начинаю верить, что узкое место — это сочетание чтения файлов и беспроводной передачи на Android.   -  person Soumya Simanta    schedule 06.04.2011


Ответы (3)


Накладные расходы на HTTP-соединение исходят из заголовков, которые он отправляет вместе с вашими данными (которые в основном являются константами). Таким образом, чем больше данных вы отправляете, тем меньше заголовки «вредят вам». Однако гораздо более важным аспектом является кодирование.

Например, если вы отправляете данные, отличные от ASCII, в сочетании с типом mime application/x-www-form-urlencoded, вы рискуете резко увеличить размер ввода, поскольку символы, отличные от ASCII, должны быть экранированы.

Из спецификации:

Тип содержимого «application/x-www-form-urlencoded» неэффективен для отправки большого количества двоичных данных или текста, содержащего символы, отличные от ASCII. Тип содержимого «multipart/form-data» следует использовать для отправки форм, содержащих файлы, данные, отличные от ASCII, и двоичные данные.

Альтернативой является multipart/form-data, который эффективен для двоичных данных. Итак, убедитесь, что ваше приложение использует этот тип MIME (возможно, вы даже можете проверить это в журналах вашего сервера).

Другой метод, который может значительно сократить время загрузки, — это сжатие. Если вы загружаете данные, которые еще не сжаты (большинство форматов изображений и видео уже сжаты), попробуйте добавить к своим загрузкам сжатие gzip. Другой пост показывает подробности настройки этого в android.

Если ваши данные имеют определенный формат (скажем, изображение), вы можете изучить алгоритмы сжатия без потерь для вашего типа данных (png для изображений, FLAC для аудио и т. д.). Сжатие всегда происходит за счет процессора (батареи), так что имейте это в виду.

Помните:

Не оптимизируйте что-либо, пока не узнаете, в чем узкое место. Возможно, соединение с вашим сервером медленное, возможно, вы не можете читать из файловой системы Android достаточно быстро, чтобы передать свои данные в сеть. Запустите несколько тестов и посмотрите, что работает.

Если бы это был я, я бы не реализовал прямой подход TCP. Просто мои 2 цента, удачи!

person skabbes    schedule 05.04.2011
comment
благодаря. Мой файл уже сжат (и зашифрован). Да, я согласен с тем, что если я не знаю, что мне нужно оптимизировать, это трудно сделать :) Прямо сейчас я пытаюсь найти узкое место (если оно есть). Я также согласен с тем, что если нет существенного улучшения производительности, подход HTTP делает многое. больше смысла, и я планирую придерживаться его, если кто-то не может дать вескую причину не делать этого. - person Soumya Simanta; 05.04.2011
comment
@Soumya Он сначала зашифрован, а затем заархивирован? Если это так, вы на самом деле не получаете никаких преимуществ от ziping. См., что зашифрованные данные не могут быть легко сжаты, потому что шифрование чего-либо существенно рандомизирует данные. На самом деле хорошим тестом на шифрование является попытка заархивировать поток данных, если он не уменьшается в размере, что может означать, что он был зашифрован. - person chubbsondubs; 06.04.2011
comment
@chubbard - сначала он зашифрован, а затем заархивирован. - person Soumya Simanta; 06.04.2011
comment
Бьюсь об заклад, вы не получаете никакой компрессии тогда. Попробуйте сначала заархивировать, а потом зашифровать. Бьюсь об заклад, вы увидите, что размер будет намного меньше, если вы сделаете это таким образом. - person chubbsondubs; 06.04.2011

Нет, нет значительных накладных расходов, связанных с использованием HTTP через сырые сокеты. Однако это действительно зависит от того, как вы используете HttpClient для отправки этого файла. Вы правильно буферизуетесь между файловой системой и HttpClient? Задержка может быть не в сети, а в чтении файла из файловой системы. На самом деле вы увеличили необработанную скорость соединения в 3 раза, а увидели снижение только в 2 раза. Вероятно, это означает, что в вашем коде, на сервере или в файловой системе есть некоторая задержка. Вы можете попробовать загрузить файл из настольного клиента, чтобы убедиться, что это не сервер, вызывающий задержку. Затем посмотрите на файловую систему через put. Если все в порядке, посмотрите на код, который вы написали с помощью HttpClient, и посмотрите, можно ли его оптимизировать.

person chubbsondubs    schedule 05.04.2011
comment
Не могли бы вы объяснить - правильно ли вы выполняете буферизацию между файловой системой и HttpClient? - person Soumya Simanta; 05.04.2011
comment
Поток FileInputStream = новый FileInputStream(файл) не буферизует данные. Таким образом, в зависимости от того, как вы работаете с потоком, это будет происходить в файловой системе каждый раз, когда вы вызываете read(). Решение состоит в том, чтобы обернуть это потоком InputStream = new BufferedInputStream(new FileInputStream(file)); Таким образом, BufferedInputStream обязательно захватит 8 КБ (по умолчанию) и кэширует его для более быстрого чтения, и вы будете совершать меньше переходов между файловой системой и вашей программой. - person chubbsondubs; 06.04.2011
comment
Та же идея существует между FileInputStream и HTTPClient. Попробуйте написать достаточно большие буферы между ними, чтобы поддерживать высокую пропускную способность. - person chubbsondubs; 06.04.2011
comment
Я попробовал простую программу только для этого файла на Android, и это заняло от 350 до 400 мс. Я пытался использовать как BufferedInputStream, так и просто FileInputStream. Оба сообщают значения в одном и том же диапазоне. - person Soumya Simanta; 06.04.2011

Также обратите внимание, что в CherryPy 3.2 система обработки тела запроса была полностью переработана, и вы можете свободно реализовывать различные обработчики в зависимости от типа носителя запроса. По умолчанию CherryPy считывает загруженные вами байты во временный файл; Я предполагаю, что ваш код затем копирует его в более постоянное место, что может быть бесполезно для вас (хотя есть веские причины безопасности для использования временного файла). См. также этот вопрос для обсуждения переименования временных файлов.

Вы можете переопределить это поведение; создайте подкласс _cpreqbody.Part с функцией make_file, которая делает то, что вы хотите, затем в инструменте замените cherrypy.request.body.part_class для этого URI. Затем разместите свой код на http://tools.cherrypy.org, чтобы все могли извлечь выгоду :)

person fumanchu    schedule 05.04.2011
comment
Спасибо за ответ. Я новичок в CherryPy, а также в Python. В моих первых прогонах я читал временный файл и записывал содержимое в новый файл. Я предположил, что это может быть причиной проблемы, поэтому я удалил его. Теперь все, что у меня есть, это def upload_time(self, myFile): out = ‹html› ‹body› myFile uploaded‹br /› ‹/body› ‹/html› return out upload_time.exposed = True - person Soumya Simanta; 05.04.2011