Загрузка файла из Интернета с возможностью в любой момент прервать загрузку

Я бы хотел загрузить файл из моей программы на Delphi в отдельной ветке, посвященной загрузке.

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

Какую функцию вы, ребята, порекомендуете использовать?

Я экспериментировал с InterOpenURL / InternetReadFile, но у него нет параметра тайм-аута. У него есть асинхронная версия, но я не смог найти никаких рабочих примеров Delphi, и я не уверен, что асинхронная версия защитит меня от зависаний ...

Раньше мне рекомендовали использовать сокеты, но TClientSocket, похоже, также не имеет функции тайм-аута.

Мне нужно быть полностью подготовленным, чтобы, если у пользователя есть проблемы с подключением к Интернету на его / ее компьютере или веб-сервер продолжает тормозить, мое приложение не зависает перед закрытием.

Пожалуйста, имейте в виду, отвечая, что я не хочу использовать ни сторонние компоненты, ни Indy. Мы приветствуем любой пример кода.

Спасибо!


person Steve    schedule 23.08.2010    source источник
comment
InternetReadFile может быть вызван с произвольным количеством байтов, извлекаемых в цикле. Если вы объедините это с проверкой завершения в своем потоке, отметьте поток для завершения при закрытии приложения и дождитесь завершения потока, у вас не должно быть никаких зависаний.   -  person Lieven Keersmaekers    schedule 23.08.2010


Ответы (4)


TWinSocketStream имеет тайм-аут. Вы в основном создаете блокирующий сокет, а затем создаете TWinSocketStream, используя этот сокет для чтения / записи. (вроде как с использованием StreamWriter). Затем вы выполняете цикл до тех пор, пока соединение не будет закрыто, поток не будет завершен или вы не достигнете ожидаемого количества байтов. Существует функция WaitForData (), которая позволяет вам проверять входящие данные без тайм-аута, поэтому вы никогда не будете «заблокированы» после этого значения тайм-аута.

Однако вам придется обрабатывать http-процесс самостоятельно. т.е. отправьте «GET», а затем прочитайте заголовок ответа, чтобы определить, сколько байтов ожидать, но это не так уж сложно.

person GrandmasterB    schedule 23.08.2010
comment
Увы, не лично. Я обычно использую библиотеку синапсов для таких вещей. Я нашел это в Интернете - похоже, это правильно, но YMMV. delphi.xcjc.net/viewthread.php?tid=29580 В этом примере используется поток сокета, но принцип будет тем же - ждать данных, читать данные, выходить, если истечет время ожидания. - person GrandmasterB; 24.08.2010

Это работает.

var
  DownloadAbort: boolean;

procedure Download(const URL, FileName: string);
var
  hInet: HINTERNET;
  hURL: HINTERNET;
  Buffer: array[0..1023] of AnsiChar;
  BufferLen, amt: cardinal;
  f: file;
const
  UserAgent = 'Delphi Application'; // Change to whatever you wish
begin
  DownloadAbort := false;
  hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  try
    hURL := InternetOpenUrl(hInet, PChar(URL), nil, 0, 0, 0);
    try
      FileMode := fmOpenWrite;
      AssignFile(f, FileName);
      try
        Rewrite(f, 1);
        repeat
          InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen);
          BlockWrite(f, Buffer[0], BufferLen, amt);
          if DownloadAbort then
            Exit;
        until BufferLen = 0;
      finally
        CloseFile(f);
      end;
    finally
      InternetCloseHandle(hURL);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
end;

На практике приведенный выше код будет частью Execute метода вашего потока, и вам не нужно DownloadAbort; intead, вы проверяете

if Terminated then
  Exit;
person Andreas Rejbrand    schedule 23.08.2010
comment
Привет! Приведенный выше код не будет работать, если InternetReadFile зависнет. Прежде чем опубликовать свой вопрос, я часами просматривал и читал отчеты об этом. - person Steve; 23.08.2010
comment
@Steve: Вы когда-нибудь наблюдали InternetReadFile зависание? Обычно вы можете доверять Windows API. В конце концов, им ежедневно пользуются миллионы разработчиков. - person Andreas Rejbrand; 23.08.2010
comment
Информацию об этом найти несложно, посмотрите, например: codeguru.com/forum/archive/index.php/t-355462.html Но если подумать, есть множество частей, где это может пойти не так: проблема с подключением пользователя к Интернету, не рабочий прокси, проблема с Wi-Fi роутером, медленный отклик интернет-сервера ... Думаю, даже InternetOpenURL может зависать. Поэтому мне нужны функции, которые не будут ждать ответа, но мгновенно возвращают управление и позволяют отменить, если это необходимо. Любые идеи? - person Steve; 23.08.2010
comment
Поместите приведенный выше код в поток и завершите поток, если он не отвечает на нормальное прерывание. - person Lars Truijens; 23.08.2010
comment
Ларс, уничтожение потока - не очень элегантное решение и может привести ко многим проблемам, включая утечки памяти ... - person Steve; 23.08.2010
comment
@Steve: Утечки памяти не имеют значения, если вы все равно закрываете приложение. ОС освободит память. - person Gerry Coll; 24.08.2010
comment
@Gerry, чтобы быть честным, я немного запутался в этом вопросе, поскольку некоторые люди говорят, что утечки памяти при выходе из программы катастрофичны, в то время как другие говорят, что именно вы делаете. Где я могу прочитать об этом подробнее? - person Steve; 24.08.2010
comment
@Steve - обратитесь к документации MS, например. msdn.microsoft.com/en-us/library /aa366803(v=VS.85).aspx The ExitProcess function in the __except block automatically releases virtual memory allocations - person Gerry Coll; 24.08.2010
comment
@Steve: В основе современной операционной системы лежит недопущение того, чтобы ни один процесс не повредил систему. Утечки памяти будут проблемой только во время запуска вашей программы; они не могут повредить операционную систему, когда ваша программа закрыта. - person Andreas Rejbrand; 24.08.2010

Благодаря GrandmasterB, вот код, который мне удалось собрать: (он находится в блоке Execute моего потока)

var
  Buffer: array [0..1024 - 1] of Char;
  SocketStream: TWinSocketStream;
  RequestString: String;
  BytesRead: Integer;
begin
  try
    ClientSocket.Active := true;
    DownloadedData := '';
    FillChar(Buffer, SizeOf(Buffer), #0);

    if not Terminated then
    begin
      // Wait 10 seconds
      SocketStream := TWinSocketStream.Create(ClientSocket.Socket, 10000);
      try
        // Send the request to the webserver
        RequestString := 'GET /remotedir/remotefile.html HTTP/1.0'#13#10 +
          'Host: www.someexamplehost.com'#13#10#13#10;
        SocketStream.Write(RequestString[1], Length(RequestString));

        // We keep waiting for the data for 5 seconds
        if (not Terminated) and SocketStream.WaitForData(5000) then
          repeat
            // We store the data in our buffer
            BytesRead := SocketStream.Read(Buffer, SizeOf(Buffer));

            if BytesRead = 0 then
              break
            else
              DownloadedData := DownloadedData + Buffer;
          until Terminated or (not SocketStream.WaitForData(2000));
      finally
        // Close the connection
        SocketStream.Free;
        if ClientSocket.Active then
          ClientSocket.Active := false;
      end;
    end;
  except
  end;

Вы должны поместить это в конструктор потока:

  ClientSocket := TClientSocket.Create(nil);
  ClientSocket.Host := 'www.someexamplehost.com';
  ClientSocket.Port := 80;
  ClientSocket.ClientType := ctBlocking;

  inherited Create(false);  // CreateSuspended := false;

А это деструктору:

  ClientSocket.Free;
  inherited;
person Steve    schedule 24.08.2010
comment
Выглядит хорошо! В ответе будут заголовки HTTP-ответа, поэтому получаемый вами файл фактически запускается после двух последовательных # D # A, которые отделяют заголовок от данных. Вы можете закрыть сокет, как только получите количество байтов, указанное в заголовке ответа - это предотвратит зависание последних 5 секунд, пока он ждет, чтобы увидеть, поступают ли еще данные. - person GrandmasterB; 24.08.2010

С сайта delphi.about.com


uses ExtActns, ...

type
   TfrMain = class(TForm)
   ...
   private
     procedure URL_OnDownloadProgress
        (Sender: TDownLoadURL;
         Progress, ProgressMax: Cardinal;
         StatusCode: TURLDownloadStatus;
         StatusText: String; var Cancel: Boolean) ;
   ...

implementation
...

procedure TfrMain.URL_OnDownloadProgress;
begin
   ProgressBar1.Max:= ProgressMax;
   ProgressBar1.Position:= Progress;
end;

function DoDownload;
begin
   with TDownloadURL.Create(self) do
   try
     URL:='http://0.tqn.com/6/g/delphi/b/index.xml';
     FileName := 'c:\ADPHealines.xml';
     OnDownloadProgress := URL_OnDownloadProgress;

     ExecuteTarget(nil) ;
   finally
     Free;
   end;
end;

{Примечание: свойство URL указывает на то, что имя файла в Интернете является локальным файлом}

person user430051    schedule 24.08.2010