【问题标题】:InternetOpenUrl only returns after entire HTTP response is downloadedInternetOpenUrl 仅在下载整个 HTTP 响应后返回
【发布时间】: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

标签: c++ winapi http wininet


【解决方案1】:

我知道回答你自己的问题可能不礼貌,但我相信我找到了问题所在。

重启后(在自动更新上浪费了很多、很多、很多分钟)我再次尝试,但遇到了同样的问题,但我向 Alex K. 和 JJ 的 cmets 建议这不是预期的行为,并开始调查机器上运行的可能干扰的软件。

在终止了许多应用程序,关闭了许多服务后,我偶然发现了一项服务,我真的希望不会有这种效果,但它确实发生了。

我关闭了“Kaspersky Lab Network Agent”,嘿-presto,InternetOpenUrl 在下载 HTTP 响应开始后大约 2 秒返回。我更喜欢立即下载,但 75 秒下载中的一两秒至少让 WinINET 有时间处理标题并进行可能需要的任何预处理。

事实证明,如果我不从 InternetReadFile() 读取数据,则下载永远不会完成(如 Charles 所见),这意味着(希望)InternetReadFile() 确实是 recv() 调用的包装器(如我所料)。

网络代理服务的连续重新启用和禁用验证了这一发现。我想以某种方式最终证明(或反驳)这一点。

事实证明,我(阅读:IT 安全部门)选择的防病毒及其拦截所有网络层通信保护似乎是问题的原因。

【讨论】:

  • 布莱恩,干得好。这是一个很好的案例研究。我们的软件环境在现实世界中的复杂性的一个很好的例子。
  • 我会接受我自己的答案(同样可能不礼貌),但是我鼓励任何人证明我错了或提供更详细的解释(因为对遇到同样问题的其他人有用,寻找一个解决方案)。
  • 你可以在局域网上使用http代理获得基本相同的效果。代理通过 WAN 缓慢下载整个文件,然后通过 LAN 将文件快速呈现给您的 PC。在我的情况下,第一个数据到达之前的延迟是 20 秒,然后整个文件几乎立即到达。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 2014-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多