Delphi. Как разделить файл на “чанки” (chunks), передать его на сервер и собрать обратно

86

87

В данном посте посмотрим, как разделить файл на части (так называемые chunks), передать его на сервер и собрать там обратно. Я использую Indy.

idHTTPServer

idHTTP

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

83

Если файл меньше размера чанка, то тогда получится так…

80

Вообще, я разрабатывал компонент для работы по HTTP протоколу, для просмотра, удаления, отправки файлов на сервер и др., по пути попалась эта задача. Она показалась мне нетривиальной. Думаю, многие программисты её решали, и каждый её решал по своему, я лишь выкладываю свою версию. Мало ли кому пригодится)

Длительность каждого блока кода я измерял с помощью конструкции

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

Исходники

Эта такая большая и нетривиальная задача, что проще смотреть и изучать исходники, кто пойдет по моим следам, вот держите. Я работал в Delphi Seattle

370_ps_httpclient_dev

373_ps_httpclient_dev (исправлена ошибка с отправкой файлов с русскими именами)

Проектная группа состоит из 3 частей

Сервер – простое VCL приложение с компонентом idHTTPServer

Клиент – компонент, созданный на основе TPanel, с idHTTP на борту

Ну и тестовая программа, на которую погружается разрабатываемый компонент

85

88

Общий алгоритм

-Подготовить директории для временных файлов на клиенте и сервере

-Создать новое, уникальное имя, основанное на GUID

-Разделить файл на чанки при помощи TFileStream

-Подготовить информационный файл (число чанков, их имена и др.) и отправить его на сервер

-Отправлять по 1 чанку и получать положительный ответ. Если положительного ответа не получено – программа прекращает отправку и прерывается.

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

-Удалить временные директории

По ходу дела также идет логирование времени выполнения операций.

Структура модулей

81

Пример использования

Код основной процедуры

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

Итак, поехали, сверху вниз, слева направо.

HTTPRequests это модуль, в котором собраны запросы на сервер. К примеру, проверку соединения с сервером, я делаю таким образом

На idHTTPServer, соответственно, код ответа выглядит следующим образом, в CommandGet

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

Далее, нам понадобится функция, для измерения размера файла, я использовал следующую

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

Нам также нужно создать уникальное имя на сервере. Чтобы не думать об этом, я полагаюсь на GUID. Кроме того, это имя должно быть ограничено в длине. Я использовал комбинированный подход – оставляя часть имени, скажем, 10 символов, и добавляя GUID. Вот метод для создания нового уникального имени, на основе старого

Файлы я решил хранить на сервере в следующей структуре.

При загрузке файла на сервер

То есть, конечная папка, это папка какого-то пользователя за конкретный день. Кроме того, есть папка temp, для создания подпапок чанков, для каждого загружаемого файла

То есть, в момент работы программы, это будет выглядеть примерно так…

84

После успешной загрузки – и сборки исходного файла из чанков – эта папка будет полностью удалена.

Соответственно функции для создания временных папок выглядят так…

Также нам понадобится временная папка на клиенте, для создания в ней чанков из отправляемого файла

И ещё понадобится папка и файл для логов, пока решил хранить их в папке каждого отдельного дня, то есть так…

Preparation

Следующую часть основного кода, я назвал Preparation, здесь получается размер файла, готовятся директории на клиенте и сервере

Sending one whole file if its size < chunkSize

Суть этой части кода в чем? Отправляем файл на сервер и запускаем таймер, который шлет запросы на сервер – а не пришел ли файл на сервер? Конечно, тут можно было обойтись более простым способом – дождаться ответа сервера, но я сделал так как сделал, потому что это более универсально, если скажем, нужно отправлять несколько запросов на сервер, и ждать, пока все они выполнятся – тут лучше централизовать это ожидание. Я сделал это через таймер. И кроме того, отправка файла может затянуться на время, большее времени ожидания ответа от сервера.

Таймер проверки наличия файла на сервере

 

Sending file, dividing on Chunks

Разберем следующую часть основного кода…

Здесь важно понять, как разделить файл на чанки? Я сделал это так…

Как собрать файл из чанков?

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

Как отправить информацию о чанках на сервер?

Нам нужен некоторый файл, который бы содержал в себе информацию о том, какие чанки вообще должны быть получены на сервере. Этот файл я назвал ChunksInfo.txt

Вот процедура сбора и отправки информации о чанках. Отправка происходит в формате JSON, обратно на сервере происходит чтение этого формата.

Отправка чанков на сервер

Таймер проверки и сборки чанков

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

Проверка целостности чанков

Пока не этом остановлюсь. Будут вопросы – пишите.

 

 

This entry was posted in Delphi, Indy. Bookmark the permalink.