Как пересылать большие файлы java

Обновлено: 05.07.2024

Я много исследовал, но не смог найти ничего подходящего по теме и, следовательно, задаю вопрос здесь.

Я хочу создать приложение, такое как Dropbox, с использованием сокетов Java, с веб-сайтом в социальной сети, где файлы, которые мы загружаем в нашу общую папку, автоматически загружаются или синхронизируются со всеми нашими системами друзей, которые мы добавили на веб-сайт социальных сетей.

До сих пор я думал, что у меня будет запущен сервер, и каждый раз, когда клиент подключается (входит в систему), я запускаю 2 обработчика, 1 для загрузки и 1 для загрузки. Обработчик DOWNLOAD будет проверять наличие новых файлов от всех моих друзей (то есть они добавляли новые файлы в свой общий каталог) каждые 5 минут и синхронизирует их. и обработчик UPLOAD загрузит файлы на сервер, отправив их в виде массива байтов, когда обработчик получит их от клиента. Клиент отправляет данные на сервер с помощью наблюдателя за каталогами для отслеживания изменений в каталоге.

Теперь вопрос в том, чтобы запустить 2 потока на клиента, возможно ли это? Я думаю, что это сильно замедлит сервер, так как я могу представить, скажем, около 100 клиентов, а это означает 200 потоков. Можете ли вы, ребята, просто указать мне в правильном направлении, какой подход мне следует выбрать? Я читал о NIO и IO и запутался. Также есть ли какая-то конкретная библиотека, которая может быть полезна? Я посмотрел на Netty, apache mina, но не понял, чем они могут быть полезны.

2 ответа

Если вы думаете, что у вас будет большое количество клиентов, использование стандартных Socket и ServerSocket не сработает. Как вы уже отметили, для этого требуется 2 потока на клиента. В конце концов, это съест все ресурсы вашего сервера. Что вам нужно, так это пакет java.nio. Там вы найдете SocketChannel и ServerSocketChannel. С их помощью вы можете настроить неблокирующую связь через сокеты. Этот тип общения основан на событиях. Это означает, что у вас может быть несколько клиентов, использующих одни и те же 2 потока на сервере для чтения и записи.

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

Я новичок в потоках, могу ли я получить образец кода.

Вам необходимо обеспечить порядок написания/чтения. Если write an object → write raw bytes на клиенте, то read an object → read raw bytes на сервере. При чтении ObjectInputStream должен иметь возможность находить границу сериализованных данных объекта.

Если вы хотите поддерживать соединение сокетов долгое время и использовать его потоки несколько раз, перенос сокета Output/InputStream в ObjectOutput/InputStream не является хорошей идеей IMO. Когда вы закрываете поток объектов, он также закрывает базовый поток.

Таким образом, вы можете записать длину сериализованных данных объекта первой (длина файла содержатся в объекте, так что вам не нужно писать Явно), например, 4 байта BigEndian кодируемого int . Затем сериализуйте объект в ByteArrayOutputStream и напишите байты в его буфере. На сервере сначала прочитайте 4 байта, декодируйте байты обратно в int и прочитайте, что много байтов в byte[] , оберните байтовый массив ByteArrayInputStream и десериализуйте объект из него.

На принимающей стороне. InputStream in = socket.getInputStream();

Я не тестировал образец кода в своем ответе. Просто чтобы показать эту идею.

Я не уверен, является ли использование механизма сериализации Java лучшим способом для простой передачи файлов. Как вы можете предположить, вы пытаетесь избежать хранения всего файла в памяти в любое время. Это можно сделать с помощью объектов с использованием шаблона прокси, но если все, что вы хотите сделать, это передача файла, это может быть не самое прямое решение. (Кроме того, он будет эффективно связывать ваш сверстник, который будет реализован на Java тоже.)

Вам не должно быть слишком сложно написать парсер для заголовка метаданных.

Класс MyClass должен реализовывать интерфейс Serializable. Затем объект этого класса можно записать в ObjectOutputStream и прочитать обратно с ObjectInputStream с помощью методов writeObject и readObject (см. Ниже).

В рамках данной статьи мы рассмотрим только первый шаблон (Request-Reply).

Собираем ØMQ и JZMQ

Перед тем как мы перейдем к коду, нам необходимо собрать сам ØMQ и библиотеки привязки (Java Bindings).

Linux

Для CentOS процесс будет выглядеть следующим образом (должен слабо отличатся для других *nix ОС).

Убедимся что у нас стоят все необходимые библиотеки:

Заберем и разархивируем последнюю стабильную версию библиотеки ØMQ с сайта разработчика (на момент написания 3.2.4).

Собираем и устанавливаем ØMQ, библиотеки попадут в директорию /usr/local/lib, это понадобится нам в будущем.

После того как мы собрали ØMQ нам необходимо собрать JZMQ. Для этого выкачиваем последнюю версию с GIT репозитория (master или tag, последний tag на момент написания — 2.2.2).

Библиотеки так же попадут в директорию /usr/lib/local. Почему это важно? Дело в том, что для того что бы использовать нативные библиотеки, Java должна знать где мы можем их найти, для этого при запуске программы мы должны указать параметр java.library.path. Для этого есть несколько способов, мы можем указать его в момент запуска приложения -Djava.library.path="/usr/lib/local", или установить его прямо во время работы программы. Мы так же можем использовать значение java.library.path, которое установлено по умолчанию. Что бы узнать, какие значение установлены по умолчанию, нужно выполнить следующую команду:


В моем случае это:

Для того что бы Java смогла найти нативные библиотеки нам достаточно перенести наши библиотеки по одному из данных адресов или залинковать их. Какой подход выбрать, выбирать лично вам.

Что бы узнать куда были установлены библиотеки после make install, достаточно выполнить команды:

Windows

Собранные dll файлы для libzmq можно скачать с официального сайта, а тут можно найти руководство по сбору JZMQ под Windows. К сожалению, собрать библиотеки с помощью CMAKE у меня не получилось, пришлось собирать libzmq и jzmq с помощью Visual Studio 2013. При этом, важно что бы сами библиотеки были собраны под архитектуру соответствующую вашей JVM (32 или 64 битная),

Если libzmq.dll и jzmq.dll добавлены в PATH, то JVM должна их найти автоматически.

Программа

Ух, мы наконец смогли поставить и настроить ØMQ и JZMQ на нашем компьютере! Настало время пустить его в дело. В качестве примера, мы попробуем с помощью реализовать протокол передачи файлов описанный в руководстве и немного улучшить его.

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

Замерим сколько времени ОС требуется для копирования данных. Данные цифры довольно приблизительны, но хотя бы мы будем иметь какую то точку сравнения.

Приступим к коду. Клиент:

  1. Команда — то что мы хотим от сервера, в данном случае команда всего одна, fetch — получить данные.
  2. Параметры команды (если присутствуют) — в случае с fetch это отступ от начала файла и размер куска данных.


Наш сервер умеет передавать всего один файл (ссылку на который он получил при старте) и отвечать всего на одну команду — fetch. Он умеет различать клиентов, но клиенты могут получить только один единственный файл. Как это улучшить я напишу чуть ниже, а пока тест и результаты измерений.

Запустим тест и посмотрим на результат.

Чтение одного гигабайта заняло 1.42 секунды. Мне тяжело сказать насколько хороший это показатель, но в сравнении с тем же Spray IO, ØMQ отрабатывает на 30-40% быстрей, при этом нагрузка на IO близка к 100% (у Spray 85-90), нагрузка на CPU ниже почти на треть.

Улучшаем протокол

Для того что бы мы могли работать с множеством файлов, мы дополнили сервер новыми объектами.

Класс реализующий DataProvider управляет получением данных, метод getBlob возвращает нам новый DataBlob который по сути является ссылкой на ресурс.

Реализация DataBlob для файла может выглядит следующим образом:

Добавив эти два метода мы позволяем выбирать какие данные хочет получить клиент и информируем клиента о размере запрошенных данных. Мы можем и далее улучшать протокол, например добавить возможность пересылки множества файлов подряд, добавить Heartbeat для определения мертвых клиентов и освобождения ресурсов и т.д.

Заключение

В следующих статьях я попытаюсь рассказать о других технологиях используемых в ArkStore, пока на очереди Akka и Semantic Web. Спасибо за внимание, надеюсь прочитанные было хоть кому то полезно!

Я создаю сервер java, который должен масштабироваться. Один из сервлетов будет обслуживать изображения, хранящиеся в Amazon S3.

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

мой вопрос: есть ли какая-либо лучшая практика в том, как кодировать сервлет java для потоковой передачи большого (>200k) ответа обратно в браузер при чтении из базы данных или другое облачное хранилище?

Я рассмотрел возможность записи файла на локальный временный диск, а затем создания другого потока для обработки потоковой передачи, чтобы поток сервлета tomcat можно было повторно использовать. Это похоже было бы Ио тяжелый.

любые мысли будут оценены. Спасибо.

Если это возможно, вы не должны хранить все содержимое файла с отбыванием в памяти. Вместо этого получите InputStream для данных и скопируйте данные в OutputStream сервлета по частям. Например:

Я согласен с Тоби, вы должны вместо этого " указать их на url-адрес S3."

что касается исключения OOM, вы уверены, что это связано с обслуживанием данных изображения? Предположим, ваш JVM имеет 256 МБ "дополнительной" памяти для использования для обслуживания данных изображения. С Справка Google, "256MB/200KB" = 1310. Для "дополнительной" памяти 2GB (в наши дни очень разумный объем) может поддерживаться более 10 000 одновременных клиентов. Тем не менее, 1300 одновременных клиентов-это довольно большое количество. Это тот тип нагрузки, который вы испытывали? Если нет, вам может потребоваться искать причину исключения OOM в другом месте.

в этом случае изображения могут содержать конфиденциальные данные.

когда я читаю через документацию S3 несколько недель назад я заметил, что вы можете создавать ключи с истекающим временем, которые могут быть прикреплены к URL-адресам S3. Таким образом, вам не придется открывать файлы на S3 для общественности. Мое понимание техники таково:--2-->

Почему бы вам просто не указать их на url S3? Взятие артефакта из S3, а затем передача его через ваш собственный сервер мне побеждает цель использования S3, которая заключается в разгрузке полосы пропускания и обработке обслуживания изображений в Amazon.

Я видел много кода, такого как ответ Джона-василефа (в настоящее время принятый), плотный цикл чтения фрагментов из одного потока и записи их в другой поток.

аргумент Я против ненужного дублирования кода, использовать IOUtils Апача. Если вы уже используете его в другом месте, или если другая библиотека или фреймворк, который вы используете, уже зависит от него, это одна строка, которая известна и хорошо протестирована.

в следующем коде, я потоковая передача объекта из Amazon S3 клиенту в сервлете.

6 линий четко определенного шаблона с правильным закрытием потока кажется довольно твердым.

Я полностью согласен с Тоби и Джон Vasileff--S3-это отличный для загрузки больших мультимедийных объектов, если вы можете терпеть связанные с этим вопросы. (Экземпляр собственного приложения делает это для 10-1000MB flv и MP4s.) Например: Однако нет частичных запросов (заголовок диапазона байтов). Нужно обрабатывать это "вручную", иногда во время простоя и т. д..

Если это не вариант, код Джона выглядит хорошо. Я обнаружил, что байтовый буфер 2K FILEBUFFERSIZE является наиболее эффективным в microbench символика. Другим вариантом может быть общий FileChannel. (FileChannels потокобезопасны.)

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

  1. Place-XX: + HeapDumpOnOutOfMemoryError в вас параметры запуска JVM, на всякий случай
  2. используйте jmap на запущенной JVM (jmap-histo

есть, конечно, другие инструменты, но jmap & jhat поставляются с Java 5+ "из коробки"

Я рассмотрел возможность записи файла на локальный временный диск, а затем создания другого потока для обработки потоковой передачи, чтобы поток сервлета tomcat можно было повторно использовать. Это похоже было бы Ио тяжелый.

Ах, я не думаю, что вы не можете сделать это. И даже если бы вы могли, это звучит сомнительно. Поток tomcat, который управляет соединением, должен находиться под контролем. Если вы испытываете голодание потока, увеличьте количество доступных потоков ./ conf / server.XML. Опять же, метрики-это способ обнаружить это-не просто догадываться.

вопрос: вы также работаете на EC2? Что такое запуск JVM вашего tomcat параметры?

Если вы используете буферы 8K, у вас может быть 1000 параллельных потоков, идущих в

8Megs пространства кучи, поэтому вы определенно делаете что-то неправильно.

кстати, я не выбрал 8K из воздуха, это размер по умолчанию для буферов сокетов, отправьте больше данных, скажем, 1Meg, и вы будете блокировать стек tcp/ip, содержащий большой объем памяти.

вы должны проверить две вещи:

  • вы закрываете поток? Очень важно!--4-->
  • возможно, вы даете потоковые соединения "бесплатно". Поток не большой, но много много потоков одновременно могут украсть всю вашу память. Создайте пул, чтобы определенное количество потоков не могло выполняться одновременно

в дополнение к тому, что предложил Джон, вы должны неоднократно промывать выходной поток. В зависимости от вашего веб-контейнера возможно, что он кэширует части или даже все ваши выходные данные и сбрасывает их сразу (например, для вычисления заголовка Content-Length). Это сожжет немало воспоминаний.

Если вы можете структурировать свои файлы так, чтобы статические файлы были отдельными и в своем собственном ведре, самая быстрая производительность сегодня, вероятно, может быть достигнута с помощью Amazon S3 CDN,CloudFront.

Читайте также: