Служба для перемещения файлов
Опубликовано 11.08.2011 г. 23:37
Возникла необходимость написать службу для перемещения файлов. Службы (сервисы) я сам никогда не писал, и даже не задумывался как их писать. Но было такое подозрение, что с помощью Delphi сделать это совсем не сложно.
Ситуация была такая: имеется сервер под управлением Windows, на ней запущен FTP сервер. По сети на фтп сервер закидываются изображения с камер видео наблюдения. С каждой камеры в определенную папку. Что требуется - перекидывать файлы из исходных папок в новые вида ГГГГ/ММ/ДД/ЧЧ. Носитель информации (ЖД и логический раздел) тот же, так что процедура перемещения времени не занимает, т.е фактически файлы все остаются на своих местах, меняется лишь структура дерева файловой системы (впрочем, это не важно). Сначала я решил было, что потребуется некоторый мониторинг состояния файловой системы, и надо будет обрабатывать события создания файла для заданной папки. Но узнав что изображения в папки попадают достаточно плотным потоком - от 1 до 25 штук в секунду, от подобного мониторинга я сразу отказался. Куда проще через определенные интервалы времени перекидывать все фото которые есть. Создать службу в Delphi оказалось совсем просто. Александр Алексеев написал хорошую вводную статью по разработке служб в Delphi, так что прочитав ее можно без особого труда создать свою. Статье уже 3 года, но кажется за это время особо ничего не изменилось, основные принципы остались прежними. Каким образом будет работать наша служба? Во-первых при запуске мы узнаем из конфигурационного файла сколько у нас подключенных камер, а также исходную директорию, и директорию назначения для каждой камеры. По числу камер мы создаем рабочие потоки. Задача потока весьма проста: после запуска поток проверяет наличие файлов в исходной папке, если они есть, то он начинает их перемещать. За одну порцию поток перемещает максимум тысячу файлов. После копирования файлов поток решает, что делать далее. Тут есть два варианта. Если файлов в папке много, то это значит, что мы переместили целую тысячу (т.е максимум что могли), и скорее всего там есть еще файлы, и тогда нам некогда отдыхать, мы сразу приступаем к перемещению следующей партии. Если же поток из максимума переместил только, например, 100 файлов, о видимо скорость поступления новых изображений не очень велика, и можно подождать - сделать паузу. Скорость появления новых снимков зависит от камеры. Если камера фиксирует движение - то создаются снимки. Если движения нет, то снимки так же создаются, но скорость их создания мала - 1 кадр в секунду. Такая ситуация может происходить, например, в ночное время. Т.е. при такой скорости нам не нужно постоянно проверять наличие новых файлов, а можно приостанавливать работу. 1 кадр/сек = 60 кадров/мин = 600 кадров за 10 минут. Т.е в принципе проспав 10 минут, мы можем потом за один заход переместить их все (даже с 40% запасом). Класс рабочего потока описан следующим образом:
TFileMoveThread = class(TThread) private FSourceDir : string; FDestDir : string; FThreadDone : TFileMoveThreadDone; FFilesMoved : integer; FId : integer; procedure DoThreadDone(); protected procedure Execute(); override; procedure MoveFiles(); public constructor Create(aID : integer; aCR : TCameraRec); destructor Destroy(); override; property OnDone : TFileMoveThreadDone read FThreadDone write FThreadDone; property FilesMoved : integer read FFilesMoved; property CameraID : integer read FId; end;из переменных членов класса на самом деле используются лишь FFilesMoved : - количество скопированных за проход файлов, FDestDir & FSourceDir - директории. При перемещении файлов в процедуре MoveFiles нам потребуется одна вложенная функция - сравнение времени создания файлов:
function TimeEquals(a,b : TSystemTime):boolean; begin result := (a.wYear = b.wYear) and (a.wMonth = b.wMonth) and (a.wDay = b.wDay) and (a.wHour = b.wHour); end;такое сравнение вызвано тем, что путь директории куда мы перемещаем нам нужно указывать с точностью до часа, т.е год, месяц, число и час. Теперь непосредственно перемещение файлов. Здесь я не использовал структуру TDirectory для получения файлов, поскольку она все таки таит в себе целую цепочку вызовов. А для реализации службы хочется сделать код поотиматльнее. Так то я решил напрямую использовать функции FindFirst/FindNext/FindClose:
procedure TFileMoveThread.MoveFiles(); var sr : TSearchRec; fileName : string; destDir : string; ft : TFileTime; sft, pft : TSystemTime; // function TimeEquals(a,b : TSystemTime) ..... begin FFilesMoved := 0; if FindFirst(FSourceDir + '*.*', 0, sr) 0 then exit; zeroMemory(@pft, sizeof(pft)); try repeat if terminated then exit; FileName := FSourceDir + sr.Name; FileTimeToLocalFileTime( sr.FindData.ftCreationTime, ft); FileTimeToSystemTime( ft, sft); DestDir := Format('%s\%d\%d\%d\%d\',[FDestDir,sft.wYear, sft.wMonth, sft.wDay, sft.wHour]); if not TimeEquals(sft, pft) then begin if not DirectoryExists(DestDir) then ForceDirectories(DestDir); end; pft := sft; MoveFile(PChar(FileName), PChar(DestDir + sr.Name)); inc(FFilesMoved); until (FindNext(sr) 0) and (FFilesMoved < MAX_FILES_MOVED); finally SysUtils.FindClose(sr); end; end;При начале перемещения мы обнуляем счетчик перемещенных файлов, и пробуем найти новые файлы в папке по маске "*.*". Затем мы поочередно начинаем перемещать файлы, каждый раз мы проверяем, не завершается ли работа потока, получаем время создания файла, формируем путь к директории назначения. Далее при условии, если директория не существует - мы создаем ее. Но однако проверять существование мы будем не каждый раз. А только в том случае - если у нас сменился час на фотографии. Это может повысить эффективность, если файлы у нас считываются в порядке их создания, поскольку вызов ForceDirectories для создания директории приводит к рекурсивным вызовам и дисковым операциям. Следующим шагом мы перемещаем файл, и увеличиваем счетчик перемещенных файлов. Такие операции мы продолжаем пока можем найти файлы, либо пока счетчик не достигнет максимального значения в 1000. Теперь непосредственно метод Execute потока. Что он делает - перемещает файлы, на основании того, сколько файлов было перемещено, поток выходит в сон. Однако сон будет посекундный, поскольку мы не можем просто вызвать sleep(600*1000), т.е усыпить поток на 10 минут, так как при остановке службы она у нас просто "зависнет". Так что эти 600 секунд поток будет спать по одной секунде, проверяя свое состояние. Для определения того, сколько времени мы можем отдохнуть следует применить небольшие знания математики. Конечно, мы можем взять любую убывающую функцию. Наша цель - выбрать такую зависимость, что при 0 скопированных файлов мы уходим в сон на 600 сек, а при тысяче - не спим вовсе. Конечно для таких целей мы можем выбрать и линейную зависимость, но это не интересно. Куда более заманчиво выглядит, например, ветвь гиперболы f(x) = 1/x. Либо на эту роль может подойти -log(x). Поскольку скорость их изменения не постоянна (в отличии от прямой f(x) = kx+b , где производная (скорость изменения) f'(x) = k постоянна). Для гиперболы есть одно ограничение, она симметрична, т.е если мы попытаемся приблизить ее к Ох, то она также приблизится и к Оу. Таким образом она нас не удовлетворяет. С логарифмом мои взаимоотношения не сложились. А вот единица деленная на экспоненту очень даже хороша. Экспонента настолько быстро растет, что нам не нужно беспокоится чтобы в значении 1000 она стремилась к нулю (1/exp). Заметим, что при значении х = 0, результат 1/exp(x) будет равен 1. Это значит, что для достижения 600 секунд, нам потребуется коэффициент 600. А прогиб экспоненты мы можем варьировать коэффициентом при х. В конечном счете функция зависимости времени от количества файлов была выбрана T(x) = 600 / exp(x/200). График такой заивимости на интервале от 0 до 1000 можно просмотреть воспользовавшись интересным сервисом WolframAlpha.com: график. С уменьшением коэффициента при Х, мы можем приближать ее к оси Х. Кто не знает - WolframAlpha это весьма интересный поисковик, только ищет он ссылки не на вэб-страницы, а информацию. Так сказать некоторая энциклопедия. Например, можно спросить его что-нибудь про Россию, и получим в ответ различную информацию. С таким же успехом мы можем попросить ее решить уравнение, либо посчитать интеграл (при чем для сложных она даже расписывает действия по упрощению), или даже узнать сколько дней до нового года. В общем рекомендую вам покликать в строке запроса по ссылке Random и секцию примеров Так что метод Execute() потока следующий:
procedure TFileMoveThread.Execute(); const max_sleep_time = 600; var timeToSleep : integer; begin try while not terminated do begin try MoveFiles(); except end; TimeToSleep := round(max_sleep_time / exp( FFilesMoved / 200)); while TimeToSleep > 0 do begin if terminated then break; Sleep(1000); dec(timeToSleep); end; end; finally Synchronize(DoThreadDone); end; end;Как видно цикл работы бесконечен. При остановке/паузе служба устанавливает свойство terminated потока в значение true. И его работа завершается. При остановке службы используем функцию WaitForMultiplеObjects:
SetLength(th, FWorkThreads.Count); for i := 0 to FWorkThreads.Count - 1 do begin th[i] := FWorkThreads[i].Handle; FWorkThreads[i].Terminate(); end; WaitForMultipleObjects(FWorkThreads.Count, @th, true, INFINITE); FWorkThreads.Free();где th - array of THandle, а объекты потоков перечислены в списке FWorkThreads. Так состоялось мое первое знакомство с реализацией служб для Windows, оказалось это совсем не сложно. Однако, проектирование качественных служб требует хороших навыков работы с потоками.
12.08.2011 в 01:39
Corresponding quantities:
Approximate heat production: 9.8 GW (gigawatts)
Матрица наступает!
12.08.2011 в 02:00
12.08.2011 в 20:00
2. Подписываться на обновления папки куда лучше чем перепроверять. Другое дело, что кидаться перемещать есть смысл не по первому чиху.
3. Зачем много потоков? Хватит пула из 1 (одного), которому можно подкидывать задачки по перемещению.
15.08.2011 в 21:21
2. Ну вот честно говоря не знаю, будет ли лучше ловить тысячи "обновлений" в минуту, либо единожды потом прочитать директорию. С одной стороны если обновления, то у нас нет дополнительных файловых операций на чтение списка файлов в директории. С другой стороны, кто знает что с этим файлом произойдет с того момента как мы "обновление" про него получили, и до тех пор когда мы таки решим что его следует переместить.
3. будет более сложный механизм, хотя чем меньше число потоков тем лучше конечно.
16.10.2011 в 19:50
except
on e: exception do begin
.....
end
если будет exception в procedure TFileMoveThread.Execute();
то ВСЕ упадет ! ...............
16.10.2011 в 19:01