Форум по Delphi программированию

Delphi Sources



Вернуться   Форум по Delphi программированию > Все о Delphi > Интернет и сети
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 04.05.2015, 11:19
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию Скачивание файла в несколько потоков

Пытаюсь организовать загрузку файла (одного файла) в несколько потоков, как, например, в Free Download Manager. Поиски в инете привели к следующему результату:
Код:
type
    TDLoad = class(TThread)
    private
     Strim: TMemoryStream;
     {вариант:  Strim: TFileStream; - тут пока не ясно}
     URL: String;
     S, E: Cardinal; //Начальный и конечный байт для скачивания
    protected
     procedure Execute; override;
    public
    end;

{ TDLoad }

procedure TDLoad.Execute;
var h: TIdHTTP;
     Strim: TMemoryStream;
begin
 inherited Execute;
 h:=TIdHTTP.Create(nil);
 h.HandleRedirects:=True; //задаем
 h.Request.UserAgent:=    //нужные 
 h.Request.Referer:=        //параметры запроса
 h.OnWork:=              //здесь можно визуализировать  
 h.OnWorkBegin:=       //процесс с помощью ProgressBar
 h.Request.ContentRangeStart:= S; //откуда начинаем закачивать
 h.Request.ContentRangeEnd:= E;   //где заканчиваем
 Strim:= TMemoryStream.Create; //либо это нужно делать далее, в цикле
                                               //и передавать потоку уже созданный TMemoryStream
  ............................
 try
  h.Get(URL,Strim); //если все удачно, то в Strim мы имеем кусок нужного файла
 Except
  on E : Exception do
  ShowMessage('Нету'+#13#10+IntToStr(h.ResponseCode));
 end;
 h.Free;
end;
Дальше более или менее ясно:
запрашиваем размер, делим на части,
Код:
procedure TForm1.Button1Click(Sender: TObject);
var i: Byte;
    dl: TDLoad;
    ms: TMemoryStream //Как вариант  
begin
 .................................
 for i:= 0 to {кол-во частей} do
 begin
  ..................................
  ms:= TMemoryStream.Create;//вариан
  dl:= TDLoad.Create(True); 
  dl.S:= //задаем начало 
  dl.E:= //и конец части
  dl.Strim:= ms;
 end;
  ...................................
       //теперь нужно отловить завершение всех dl: TDLoad
       //и собрать все потоки в один файл очевидно понадобится TFileStream
end;
отловить завершение всех потоков я, вероятно, еще сумею, хотя и здесь не откажусь от помощи
а вот как собрать части в один файл не ясно
или есть какая то возможность записывать части сразу в FileStream?
Ответить с цитированием
  #2  
Старый 04.05.2015, 20:03
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,024
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

1. Отлов завершения потоков. Вариантов много. Например, т.к. ты заранее знаешь кол-во потоков (блоков файла), то можно создать массив, ссылку на который передавать потокам. Каждый поток там проставит флажек, что завершился. А главный поток просто проверяет массивы состояний и соотв. обновляет глобальную информацию.
2. Т.к. ты знаешь размер файла до начала его скачивания, то сначала создай соотв файл (читай - TFileStream) и ссылку на него передавай каждому потоку вместе с началом и концом. Каждый поток скачает данные во времененый буфер (TMemorystream), а потом просто запишет его в нужное место файла. Не забудь синхронизовать потоки в момент записи через CriticalSection.
3. Писать сразу в результирующий поток не получится, т.к. компоненты этого не умеют. Хотя, если подправишь сами компоненты, то можно и без промежуточного буфера обойтись.
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (04.05.2015)
  #3  
Старый 05.05.2015, 01:36
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от lmikle
можно создать массив... Каждый поток там проставит флажок, что завершился. А главный поток просто проверяет массивы состояний...
если так
Код:
procedure TForm1.Button1Click(Sender: TObject);
var   i: Byte;         TDLoad;
begin
 .................................
 for i:= 0 to {кол-во частей} do
 begin
  ..................................
  dl:= TDLoad.Create(True); 
  .....................................
 end;
  ...................................
 while {не все потоки поставили флажок (завершились)} do Sleep(1000);
end;
тогда получается - главный поток будет занят до завершения всех потоков, другой вариант - таймер, интервал, скажем секунда. В обоих случаях можно спокойно обойтись без массива, и даже не обязательно знать заранее кол-во потоков (частей файла)
Код:
var
  Form1: TForm1;
  TrCnt: Byte;
  CntLock: TCriticalSection;

implementation
Код:
procedure TForm1.Button1Click(Sender: TObject);
var i: Byte;
    dl: TDLoad;
begin
 .................................
 for i:= 0 to {кол-во частей} do
 begin
  ..................................
  CntLock.Enter; Inc(TrCnt); CntLock.Leave;
 end;
  ...................................
 while TrCnt>0 do Sleep(1000);
end;
ну или через таймер
Код:
procedure TDLoad.Execute;
var h: TIdHTTP;
     Strim: TMemoryStream;
begin
  ............................
  h.Get(URL,Strim); //если все удачно, то в Strim мы имеем кусок нужного файла
  ...............................
 h.Free;
  CntLock.Enter; Dec(TrCnt); CntLock.Leave;
end;
Цитата:
Сообщение от lmikle
2. Т.к. ты знаешь размер файла до начала его скачивания, то сначала создай соотв файл (читай - TFileStream) и ссылку на него передавай каждому потоку вместе с началом и концом. Каждый поток скачает данные во временный буфер (TMemorystream), а потом просто запишет его в нужное место файла.
Код:
type
    TDLoad = class(TThread)
    private
     Strim: TFileStream;
  ..............................
    protected
     procedure Execute; override;
    end;
 
{ TDLoad }
 
procedure TDLoad.Execute;
var h: TIdHTTP;
     Buf: TMemoryStream;
begin
................................
 Buf:= TMemoryStream.Create;
  ............................
 try
  h.Get(URL,Strim); 
 Except
  on E : Exception do ShowMessage('Нету'+#13#10+IntToStr(h.ResponseCode)); 
 end;
 h.Free;
 CntLock.Enter;
 Strim.Position:=S; //не уверен
 Strim.CopyFrom(Buf,Buf.Size); //не уверен
 CntLock.Leave;
 Buf.Free;
end;
Код:
procedure TForm1.Button2Click(Sender: TObject);
var i: Byte;
    dl: TDLoad;
    fs: TFileStream;
begin
 fs:= TFileStream.Create('D:\cashe\001.mp4',fmCreate);
 fs.Size:={.Response.ContentLength};
 for i:= 0 to {кол-во частей} do
 begin
  dl:= TDLoad.Create(True);
  dl.S:= //Начало
  dl.E:= //Конец
  dl.Strim:= fs; //здесь не уверен
  dl.Resume;
 end;
end;
только, по-моему тут нет необходимости отлавливать окончания потоков.
Цитата:
Сообщение от lmikle
..., если подправишь сами компоненты, то можно и без промежуточного буфера обойтись.
по-моему овчинка выделки не стоит, что мне даст - "без промежуточного буфера обойтись", полагаю некоторую экономию памяти. Стоит ли оно ("подправишь сами компоненты") того.
Ответить с цитированием
  #4  
Старый 05.05.2015, 03:26
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,024
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

1. А что мешает завести еще один поток, который будет заниматься этим? Один на все скачки, если их много.
2. Еще раз. В момент настройки скачки ты получаешь размер всего файла. соответственно, можешь аллоцировать место (записать нули в TFileStream в нужном кол-ве). часть файла, скачанная в TMemoryStream - ты всегда знаешь с какой позиции надо писать, а длинна - размер TMemoryStream.
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (05.05.2015)
  #5  
Старый 05.05.2015, 05:52
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от lmikle
1. А что мешает завести еще один поток, который будет заниматься этим? Один на все скачки, если их много.
ну так-то - да Кстати, у меня подозрение, что таймер как раз и работает в отдельном потоке, с другой стороны кажется - это плохой вариант, правда пока не пойму почему.
Цитата:
Сообщение от lmikle
...размер всего файла. Соответственно, можешь аллоцировать место (записать нули в TFileStream в нужном кол-ве).
Вот это вот -
Код:
 fs.Size:={.Response.ContentLength};
это то что нужно? или требуются еще какие то телодвижения?
Цитата:
Сообщение от lmikle
часть файла, скачанная в TMemoryStream - ты всегда знаешь с какой позиции надо писать, а длинна - размер TMemoryStream.
Я вроде так и сделал
Код:
 CntLock.Enter;
 Strim.Position:=S; 
 Strim.CopyFrom(Buf,Buf.Size); 
 CntLock.Leave;

Все-таки смущает:
Код:
type
    TDLoad = class(TThread)
    private
     Strim: TFileStream;
  ..............................
Код:
procedure TForm1.Button2Click(Sender: TObject);
...................
begin
 fs:= TFileStream.Create('D:\cashe\001.mp4',fmCreate);
...................
 for i:= 0 to {кол-во частей} do
 begin
  dl:= TDLoad.Create(True);
.....................
  dl.Strim:= fs;.
 end;
end;
Ответить с цитированием
  #6  
Старый 06.05.2015, 20:16
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от lmikle
... Не забудь синхронизовать потоки в момент записи через CriticalSection...
CriticalSection - не подходит, Write Stream Error. Надо через Synchronize.
Ответить с цитированием
  #7  
Старый 06.05.2015, 21:42
Аватар для M.A.D.M.A.N.
M.A.D.M.A.N. M.A.D.M.A.N. вне форума
Sir Richard Abramson
 
Регистрация: 05.04.2008
Сообщения: 5,505
Версия Delphi: XE10
Репутация: выкл
По умолчанию

Цитата:
Сообщение от Помидоркин
CriticalSection - не подходит, Write Stream Error. Надо через Synchronize.
Странно, небыло никогда такой проблемы со стримами при многопоточной загрузке. Просто держал файлы открытыми и обращался к нужному стриму. Обычного монитора хватало для разграничения доступа.
__________________
— Как тебя понимать?
— Понимать меня не обязательно. Обязательно меня любить и кормить вовремя.


На Delphi, увы, больше не программирую.
Рекомендуемая литература по программированию
Ответить с цитированием
  #8  
Старый 06.05.2015, 22:54
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от M.A.D.M.A.N.
Странно, не было никогда такой проблемы со стримами ....
Да пёс его знает, но факт остается фактом - в критической секции никак писАть не хочет, а в синхронайз пожалуйста.
Ответить с цитированием
  #9  
Старый 07.05.2015, 03:59
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Вот, собственно, что получилось в итоге:
Код:
type
    TDLoad = class(TThread)
    private
     URL: String;
     S, E: Cardinal;
     Buf: TMemoryStream;
     procedure WriteStrim;
    protected
     procedure Execute; override;
    public
    end;

{ TDLoad }

procedure TDLoad.Execute;
var h: TIdHTTP;
begin
 h:=TIdHTTP.Create(nil);
 {настраиваю запрос}
 h.OnWork:= Form1.IdHTTPWork; 
 h.Request.ContentRangeStart:= S;
 h.Request.ContentRangeEnd:= E;
 Buf:= TMemoryStream.Create;
 try
  h.Get(URL,Buf);
 Except
  on E : Exception do
 //тут надо-бы предусмотреть обработку ошибки
 end;
 h.Free;
 Buf.Position:=0;
 Synchronize(WriteStrim);
 Buf.Free;
 CntLock.Enter; Dec(TrCnt);   CntLock.Leave; // TrCnt: Byte - счетчик потоков,  CntLock: TCriticalSection;
end;

procedure TDLoad.WriteStrim;
begin
 FileStrim.Position:=S;
 FileStrim.CopyFrom(Buf,Buf.Size);
end;
Код:
procedure TForm1.FormCreate(Sender: TObject);
begin
 CntLock:=TCriticalSection.Create;
 Link:= 'http://dedowsk-beauty.ru/0/Gloria_Gaynor__I_Will_Survive.mp3';
end;

procedure TForm1.Button2Click(Sender: TObject);
var i: Byte;
     dl: TDLoad;
     h: TIdHTTP;
     CntntLen: Int64;
begin
 h:=TIdHTTP.Create(nil);
  {настраиваю запрос}
 try
  h.Head(Link);
 Except
  on E : Exception do begin
  ShowMessage('Нету'+#13#10+IntToStr(h.ResponseCode));
  Exit; end;
 end;
 CntntLen:=h.Response.ContentLength;    h.Free;
 FileStrim:= TFileStream.Create('D:\cashe\Gloria_Gaynor__I_Will_Survive.mp3',fmCreate);
 FileStrim.Size:=CntntLen;
 PrBar.Max:= SecLen; // тут пришлось приделывать "костыли"
 PrBar.Position:=0;
 TrCnt:=0;
 for i:= 0 to CntntLen div SecLen  do //const SecLen: Integer = 2097152; (2MB)
 begin
  dl:= TDLoad.Create(True);
  dl.FreeOnTerminate:=True;
  dl.URL:= Link;
  dl.S:= i*SecLen;
  dl.E:= (i+1)*SecLen-1;
  dl.Resume;
  CntLock.Enter; Inc(TrCnt); CntLock.Leave;
 end;
 while TrCnt>0 do begin
  Label1.Caption:=IntToStr(TrCnt);
  Sleep(1000);
 end;
 Label1.Caption:=IntToStr(TrCnt);
 PrBar.Position:=0;
 FileStrim.Free;
end;
Код:
procedure TForm1.IdHTTPWork(Sender: TObject; AWorkMode: TWorkMode;
  const AWorkCount: Integer);
begin
 PrBar.Position:= AWorkCount;
end;
Загвоздка в ProgressBar-е, Position надо задавать складывая AWorkCount всех потоков или запоминать предыдущее состояние AWorkCount и отнимать его от текущего. По-этому сделал так, как сделал, прогресс скачет немного вперед-назад, но общее представление о ходе процесса получить можно.
Может быть у кого будут какие идеи по-поводу прогресса?
Ответить с цитированием
  #10  
Старый 07.05.2015, 04:16
Аватар для Alegun
Alegun Alegun вне форума
LMD-DML
 
Регистрация: 12.07.2009
Адрес: Богородское
Сообщения: 3,025
Версия Delphi: D7E
Репутация: 1834
По умолчанию

Может так
Код:
if PrBar.Position < AWorkCount then PrBar.Position:= AWorkCount;
Ответить с цитированием
Этот пользователь сказал Спасибо Alegun за это полезное сообщение:
Помидоркин (08.05.2015)
  #11  
Старый 08.05.2015, 00:26
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от Alegun
Может так
Код:
if PrBar.Position < AWorkCount then PrBar.Position:= AWorkCount;
пробовал, не понравилось, без if все же нагляднее.
Вижу только два варианта:
1. наследовать от IdHTTP, добавить поле типа Int64 и запоминать в него предыдущее значение AWorkCount
2. ProgressBar для каждого потока, но уж больно громоздко.

Теперь пытаюсь все это безобразие прикрутить к динамической форме, пока не очень-то получается
Ответить с цитированием
Ответ


Delphi Sources

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 06:10.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources" by BrokenByte Software, 2004-2023

ВКонтакте   Facebook   Twitter