【发布时间】:2011-08-11 21:55:51
【问题描述】:
我正在使用 WinINET 编写一个下载文件实用程序,并且注意到(尤其是在大型下载时)WinINET InternetOpenUrl() 调用仅在整个 HTTP 响应下载后才返回。
我通过使用 Charles 代理工具以及 WireShark 确认了这一点,并注意到下载已完全完成,然后 WinINET 才会通知我的代码。
一些简化(同步)代码:
hInt = InternetOpen(USER_AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, 0);
DWORD dwRequestFlags = INTERNET_FLAG_NO_UI // no UI please
|INTERNET_FLAG_NO_AUTH // don't authenticate
|INTERNET_FLAG_PRAGMA_NOCACHE // do not try the cache or proxy
|INTERNET_FLAG_NO_CACHE_WRITE; // don't add this to the IE cache
hUrl = InternetOpenUrl(hInt, szURL, NULL, 0, dwRequestFlags, NULL);
if (hUrl)
{
// <only gets here after entire download is complete>
InternetCloseHandle(hUrl);
}
InternetCloseHandle(hInt);
文档建议这会发送请求,并处理响应的标头(未完成下载),然后您应该运行 InternetReadFile() 循环,直到它返回 @ 987654326@ 和 dwNumberOfBytesRead 为 0。
来自 MSDN
InternetOpenUrl Function: InternetOpenUrl 函数解析 URL 字符串,建立与服务器的连接,然后准备 下载由 URL 标识的数据。然后应用程序可以使用 InternetReadFile [...] 来检索 URL 数据。InternetReadFile Function: 为确保检索到所有数据,应用程序必须继续调用 InternetReadFile 函数,直到该函数返回 TRUE 并且 lpdwNumberOfBytesRead 参数为零。
我也尝试过使用异步方法,并注意到同样的事情。具体来说,INTERNET_STATUS_RESPONSE_RECEIVED 只有在下载完成后才会发送到注册的回调方法。这意味着我的客户端只有在下载完成后才能开始访问数据。
以类似的方式,我也实现了一个使用 WinHttp 库的版本,并注意到完全相同的结果。
当涉及到超时时,这会让事情变得棘手。如果下载超过了超时时间(默认为 30 秒),InternetOpenUrl() 将失败。
所以我有两个问题:
如果这是 WinInet 和 WinHttp 库的预期行为,为什么文档建议循环通过 InternetReadFile() 调用,为什么不直接读取整个缓冲区(毕竟 WinINET 已经有了)?
我理解提供该功能,因为您并不总是希望分配 150MB 的内存块,但提供的借口是您不知道有多少数据可用......但 WinINET 已经完成了下载。
如果recv() 方法只是对临时文件或 IE 缓存中的文件(或者更糟的是浪费的内存块)的抽象,为什么还要让它看起来非常像被包裹起来的方法?
我应该将超时长度设置为什么?如果在超时之前我永远不知道数据有多大,那么我如何决定将超时值设置为什么?
这是预期的行为吗?如果是,有没有办法在数据流下时获取数据?
在连接速度较慢或文件较大的情况下,很可能会在整个下载完成之前对数据进行大量工作。在 HTTP 的经典 Berkley 套接字重新实现中,循环通过 recv() 调用将为我提供数据,这最终是我需要的。
是的,我可以使用简单的套接字重新编写一个实现,但我宁愿不必浪费时间支持整个 HTTP 规范和 SSL 加密,更不用说 WinINET 中的代理支持了。
【问题讨论】:
-
您看到与 HttpOpenRequest > HttpSendRequest 相同的行为?
-
不幸的是,我在同步和异步实现中都做到了。 HttpSendRequest 要么像 InternetOpenUrl 一样阻塞(同步),要么仅在 HTTP 响应完全下载(异步)后发送 INTERNET_STATUS_REQUEST_COMPLETE 通知。
-
某处有些奇怪——我已经多次使用 wininet 库,它们的行为与文档描述的一样。也许将您的故障排除扩展到 wininet 设置或响应内容?
-
对我来说同样的价值,HttpSendRequest 应该立即返回(排除网络问题),此时您可以开始 ReadFile'ing