从互联网下载文件并能随时中止下载的方法

7

我想从我的Delphi程序中下载一个文件,并在专门用于下载的单独线程中执行。

问题是,主程序可以随时关闭(因此下载线程也可能随时被终止)。所以,即使没有连接或服务器耽搁了几秒钟,我也需要一种方法,在一两秒钟内终止下载线程。

你们推荐使用哪个函数呢?

我已经尝试过InterOpenURL/InternetReadFile,但它没有超时参数。它有一个异步版本,但我找不到任何可以工作的Delphi示例,而且我不确定异步版本是否能保护我免受挂起的影响......

之前有人建议我使用socket,但TClientSocket似乎也没有超时函数。

我需要做好充分准备,以便如果用户的计算机出现互联网连接问题或者Web服务器一直耽搁,我的应用程序在关闭之前不会挂起。

请注意,当回答问题时,我不想使用任何第三方组件或Indy。任何示例代码都将不胜感激。

谢谢!


InternetReadFile 可以在循环中调用任意数量的字节进行获取。如果您将此与线程中的 Terminated 检查相结合,在关闭应用程序时标记线程以终止并等待线程终止,您就不会遇到任何“挂起”的情况。 - Lieven Keersmaekers
4个回答

5

这个有效。

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;相反,您可以检查

if Terminated then
  Exit;

@Steve:你有没有观察到 InternetReadFile 函数卡住的情况?通常来说,你可以相信 Windows API。毕竟,它每天被数百万开发者使用。 - Andreas Rejbrand
1
将上述代码放入一个线程中,如果它没有响应正常的中止,则终止该线程。 - Lars Truijens
2
@Steve:如果你要关闭应用程序,内存泄漏并不重要。操作系统会回收内存。 - Gerry Coll
@Gerry,说实话我对这个问题有点困惑,因为有些人说程序退出时的内存泄漏是灾难性的,而另一些人则像你一样。我在哪里可以阅读更多关于这方面的内容? - Steve
@Steve - 参考微软文档,例如http://msdn.microsoft.com/en-us/library/aa366803(v=VS.85).aspx __except块中的ExitProcess函数会自动释放虚拟内存分配 - Gerry Coll
显示剩余4条评论

2
感谢GrandmasterB,以下是我成功编写的代码(在我的线程执行块中):
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;

看起来不错!响应将包含 HTTP 响应头,因此您收到的文件实际上将在两个连续的#D#A之后开始,它们将头和数据分开。一旦您收到响应头中指定的字节数,就可以立即关闭套接字-这将防止最后5秒钟挂起,等待是否还有更多数据。 - GrandmasterB

2

TWinSocketStream具有超时功能。您基本上创建一个阻塞的套接字,然后使用该套接字创建TWinSocketStream进行读/写操作(类似于使用StreamWriter)。然后,您循环直到连接关闭、线程终止或达到预期字节数。有一个WaitForData()函数可以检查传入数据并设置超时时间,所以您永远不会超过该超时值而被锁定。

但是您需要自己处理HTTP进程。即,发送“GET”请求,然后读取响应头以确定要期望多少字节,但这并不太困难。


1
哎呀,我个人不会。通常我会使用synapse库来完成这种事情。我在网上找到了这个 - 看起来是正确的,但可能因人而异。http://delphi.xcjc.net/viewthread.php?tid=29580 这个例子使用了socketthread,但原理是相同的 - 等待数据,读取数据,如果超时就退出。 - GrandmasterB

1

来自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属性指向互联网 FileName是本地文件 }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接