使用 Wininet 下载二进制文件

Posted

技术标签:

【中文标题】使用 Wininet 下载二进制文件【英文标题】:Downloading Binary Files With Wininet 【发布时间】:2011-08-05 17:14:02 【问题描述】:

我目前正在编写一个简单的程序,我想分发给我的朋友。我想要完成的是在启动程序时将一些外部二进制文件从互联网写入缓冲区。为此,我使用的是 Windows Internet(wininet)。目前,我正在使用 InternetReadFile 将文件写入我稍后在程序中使用的缓冲区。但是,文件并没有被完全读取,因为结果大小比服务器上文件的大小要小得多,当它应该相同时。

我想这样做,不使用任何外部库。

知道什么可以解决我的问题吗?

谢谢, 安德鲁

【问题讨论】:

编辑了我的答案以包含完整的工作程序。你可以试试看它是否有效? 【参考方案1】:

documentation 发表如下评论:

InternetReadFile 的操作与基本的 ReadFile 函数非常相似,但有一些例外。通常,InternetReadFile 从 HINTERNET 句柄中检索数据作为顺序字节流。每次调用 InternetReadFile 要读取的数据量由 dwNumberOfBytesToRead 参数指定,数据在 lpBuffer 参数中返回。正常读取为每次调用 InternetReadFile 检索指定的 dwNumberOfBytesToRead,直到到达文件末尾。 为确保检索到所有数据,应用程序必须继续调用 InternetReadFile 函数,直到该函数返回 TRUE 并且 lpdwNumberOfBytesRead 参数为零。

基本上,不能保证函数能准确读取dwNumberOfBytesToRead。使用lpdwNumberOfBytesRead 参数检查实际读取了多少字节。

此外,一旦总文件大小大于dwNumberOfBytesToRead,您将需要多次调用该调用。因为它一次不能读取超过dwNumberOfBytesToRead

如果您预先知道总文件大小,则循环采用以下形式:

::DWORD error = ERROR_SUCCESS;
::BYTE data[SIZE]; // total file size.
::DWORD size = 0;
::DWORD read = 0;
do 
    ::BOOL result = ::InternetReadFile(stream, data+size, SIZE-size, &read);
    if ( result == FALSE ) 
        error = ::GetLastError();
    

while ((error == ERROR_SUCCESS) && (read > 0) && ((size+=read) < SIZE));
  // check that `SIZE` was correct.
if (size != SIZE) 

如果没有,则需要将缓冲区中的数据写入另一个文件,而不是累积。

编辑(示例测试程序)

这是一个获取 *** 首页的完整程序。这会以 1K 块下载大约 200K 的 html 代码,并检索整个页面。你能运行它看看它是否有效吗?

#include <Windows.h>
#include <Wininet.h>
#include <iostream>
#include <fstream>

namespace 

    ::HINTERNET netstart ()
    
        const ::HINTERNET handle =
            ::InternetOpenW(0, INTERNET_OPEN_TYPE_DIRECT, 0, 0, 0);
        if ( handle == 0 )
        
            const ::DWORD error = ::GetLastError();
            std::cerr
                << "InternetOpen(): " << error << "."
                << std::endl;
        
        return (handle);
    

    void netclose ( ::HINTERNET object )
    
        const ::BOOL result = ::InternetCloseHandle(object);
        if ( result == FALSE )
        
            const ::DWORD error = ::GetLastError();
            std::cerr
                << "InternetClose(): " << error << "."
                << std::endl;
        
    

    ::HINTERNET netopen ( ::HINTERNET session, ::LPCWSTR url )
    
        const ::HINTERNET handle =
            ::InternetOpenUrlW(session, url, 0, 0, 0, 0);
        if ( handle == 0 )
        
            const ::DWORD error = ::GetLastError();
            std::cerr
                << "InternetOpenUrl(): " << error << "."
                << std::endl;
        
        return (handle);
    

    void netfetch ( ::HINTERNET istream, std::ostream& ostream )
    
        static const ::DWORD SIZE = 1024;
        ::DWORD error = ERROR_SUCCESS;
        ::BYTE data[SIZE];
        ::DWORD size = 0;
        do 
            ::BOOL result = ::InternetReadFile(istream, data, SIZE, &size);
            if ( result == FALSE )
            
                error = ::GetLastError();
                std::cerr
                    << "InternetReadFile(): " << error << "."
                    << std::endl;
            
            ostream.write((const char*)data, size);
        
        while ((error == ERROR_SUCCESS) && (size > 0));
    



int main ( int, char ** )

    const ::WCHAR URL[] = L"http://***.com/";
    const ::HINTERNET session = ::netstart();
    if ( session != 0 )
    
        const ::HINTERNET istream = ::netopen(session, URL);
        if ( istream != 0 )
        
            std::ofstream ostream("output.txt", std::ios::binary);
            if ( ostream.is_open() ) 
                ::netfetch(istream, ostream);
            
            else 
                std::cerr << "Could not open 'output.txt'." << std::endl;
            
            ::netclose(istream);
        
        ::netclose(session);
    


#pragma comment ( lib, "Wininet.lib" )

【讨论】:

感谢您的回复。我在 while 循环中调用了 InternetReadFile,直到它返回 true,但是文件仍然损坏并且大小不合适。 @Andrew:再次阅读文档。 TRUE 针对任何“成功”条件返回,包括“仅读取 1 个字节的数据”。它应该第一次返回TRUE,所以在它返回TRUE之前不要循环。 @Andrew:您是否尝试过不在任何地方复制数据,而只是计算函数返回的字节总数?确保这会返回正确的字节数。 我已经按照你的建议做了,但还是有问题。 如果问题仍然与此问题中的问题有关,请编辑您的帖子以包含信息,以便我提供帮助。如果没有,请随时提出另一个问题。

以上是关于使用 Wininet 下载二进制文件的主要内容,如果未能解决你的问题,请参考以下文章

InternetReadFile 仅读取 10kb

将进度条添加到wininet http上传C++

Delphi - 使用 Wininet 下载文件?

使用wininet下载文件时如何避免冻结

Delphi用WinInet用用户名和密码下载文件

如何使用winsock或wininet下载文件[关闭]