与我的一篇文章(How to retrieve a file from Internet via HTTP?)有关如何从互联网上轻松而强大地下载文件,我找到了一个可能的解决方案 - 但是它不能正常工作。
根据MS文档,下面的代码应该在我断开互联网连接后的500ms超时。但是,它看起来完全忽略了'INTERNET_OPTION_RECEIVE_TIMEOUT'设置。应用程序在下载期间冻结。此功能需要大约20-30才能实现Internet连接断开并将控制权交还给GUI。
有谁知道为什么?
function GetBinFileHTTP (const aUrl: string; const pStream: TStream; wTimeOut: Word= 500; wSleep: Word= 500; wAttempts: Word= 10): Integer;
CONST
BufferSize = 1024;
VAR
hSession, hService: HINTERNET;
Buffer : array[0..BufferSize-1] of Char;
dwBytesRead, dwBytesAvail: DWORD;
lSucc : LongBool;
lRetries, dwTimeOut: Integer;
begin
Result:= 0;
if NOT IsConnectedToInternet then
begin
Result:= -1;
EXIT;
end;
hSession := InternetOpen(PChar(ExtractFileName(Application.ExeName)), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); { The INTERNET_OPEN_TYPE_PRECONFIG flag specifies that if the user has configured Internet Explorer to use a proxy server, WinInet will use it as well. }
if NOT Assigned(hSession) then
begin
Result:= -4;
EXIT;
end;
TRY
hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
if NOT Assigned(hService) then Exit;
TRY
FillChar(Buffer, SizeOf(Buffer), 0);
{ Set time out }
dwTimeOut:= wTimeOut;
InternetSetOption(hService, INTERNET_OPTION_RECEIVE_TIMEOUT, @dwTimeOut, SizeOf(dwTimeOut)); { use INTERNET_FLAG_RELOAD instead of NIL to redownload the file instead of using the cache }
InternetSetOption(hService, INTERNET_OPTION_CONNECT_TIMEOUT, @dwTimeOut, SizeOf(dwTimeOut));
REPEAT
lRetries := 0;
REPEAT
lSucc:= InternetQueryDataAvailable( hService, dwBytesAvail, 0, 0);
if NOT lSucc
then Sleep( wSleep );
if lRetries > wAttempts
then Result:= -2;
UNTIL lSucc OR (Result= -2);
if NOT InternetReadFile(hService, @Buffer, BufferSize, dwBytesRead) then
begin
Result:= -3; { Error: File not found/File cannot be downloaded }
EXIT;
end;
if dwBytesRead = 0
then Break;
pStream.WriteBuffer(Buffer[0], dwBytesRead);
UNTIL False;
FINALLY
InternetCloseHandle(hService);
end;
FINALLY
InternetCloseHandle(hSession);
end;
Result:= 1;
end;
以下是文档:
{
INTERNET_OPTION_CONNECT_TIMEOUT Sets or retrieves an unsigned long integer value that contains the time-out value to use for Internet connection requests. If a connection request takes longer than this time-out value, the request is canceled. When attempting to connect to multiple IP addresses for a single host (a multihome host), the timeout limit is cumulative for all of the IP addresses. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption and InternetSetOption.
INTERNET_OPTION_RECEIVE_TIMEOUT Sets or retrieves an unsigned long integer value that contains the time-out value to receive a response to a request. If the response takes longer than this time-out value, the request is canceled. This option can be used on any HINTERNET handle, including a NULL handle. It is used by InternetQueryOption and InternetSetOption. For using WinInet synchronously, only the default value for this flag can be changed by calling InternetSetOption and passing NULL in the hInternet parameter.
INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT - Identical to INTERNET_OPTION_RECEIVE_TIMEOUT. This is used by InternetQueryOption and InternetSetOption.
}
编辑: 在应用程序开始下载后,我从软件上拔下电缆或(用于无线)断开互联网连接(我选择下载大文件)。它模拟网站脱机。
答案 0 :(得分:5)
连接超时显然不适用于您的测试,因为在您开始测试时(即拔插头),连接已经建立。实际上,在您开始设置超时选项之前,已经建立了连接。
接收超时的有效性也是可疑的,因为您已经开始接收响应。
最有希望的超时是断开连接超时,但MSDN表示尚未实现。
在我看来,要走的路是使用异步操作。使用InternetReadFileEx
并使用irf_Async
和irf_No_Wait
标记。如果在没有接收任何数据的情况下经过太多时间,请关闭连接。另一种选择是坚持使用同步调用,但如果下载时间过长,则从另一个线程调用InternetCloseHandle
。
答案 1 :(得分:3)
MS IE代码中存在文档错误。只能通过使用线程中的代码并重新实现超时机制来解决。
详细信息:
“本文通过创建第二个线程,显示设置超时值的InternetSetOption API错误的解决方法。 InternetSetOption不设置超时值“
http://support.microsoft.com/default.aspx?scid=kb;en-us;Q224318
(据报道链接坏了。责备MS不是我)
也许有人可以帮助在Delphi中实现这个bug修复。我个人没有C经验。即使伪Pascal的主干也会很好。
答案 2 :(得分:0)
IMO,你应该在一个帖子中运行它。线程并不一定意味着循环 - 它可以是一个“完成”线程。以这种方式运行,并且您的GUI保持响应,直到线程完成。我意识到这实际上并没有回答你的问题,但它会让你的代码变得更好。
此外,如果您在检查数据的第一个循环期间断开互联网连接,我认为它将重试10次。您应该检测,然后立即退出。
最后,当你有句柄和东西打开时,我认为你不应该使用EXIT。相反,中断,以便您仍然通过断开连接。我希望你的代码能够占用套接字。我最近在代码审查期间看到了这一点,当时有一个BREAK的EXIT内容,并且由于创建了对象并且从未释放它而导致内存泄漏。我在这里使用相同的规则。
答案 3 :(得分:0)
您确定没有进入INTERNET_OPTION_CONNECT_TIMEOUT吗?它将首先尝试连接,然后接收。
为了测试连接超时,它必须解决,但永远不要连接。为了测试读取超时,它必须连接,但从不接收任何数据。
我通常将连接超时设置为10秒,读取超时设置为30秒。不管怎么说,我还是考虑下来。