WinInet单线程断点续传下载

最近比较空闲,尝试了一下网络方面的编程,于是兴起写一个多线程断点续传下载的简单demo。于是首先在网络上搜索各种实现思路,最终决定先从一个简单的单线程断点续传下载开始。

实现思路:

每次都以如果不存在则创建的方式打开要下载的文件,然后获取其大小size,然后给URL发送请求头,带上头信息range,并以size作为要获取的数据起始位置,终止位置不写(表明要获取后面所有的数据)。然后便是不断的向文件末尾追写数据。直至某一次启动下载的时候发现返回的状态码为416,表明范围超出了,则表示文件已经下载完成了。

关键知识点:

1、需要对HTTP协议有初步的理解,主要是对请求头与返回头的格式要求进行理解。

2、要明白Http请求头中range关键字的用法。

3、学会使用Goole Chrome的审查元素功能和微软的wfetch小工具进行Http请求头和返回头信息的查看。

4、最后,当然就是编程方面的知识了,包括wininet库的使用和基本的线程和文件的操作等。

 

剩下的便是按照这个思路,去慢慢的学习相关知识,然后编程实现这个Demo了。我这里粘上自己的代码,本人英语实在很差,但是还是尝试着使用英语去注释了,看不太懂的,可以来问我。

/*

    Filename:

            main.cpp



    Function:

            单线程断点续传功能实验。

            以文件本身的大小(没有则新创建)作为本次要下载时请求数据的起始位置,每次都将读取下来的数据追加到文件末尾。



    Knowledge:

            需要具备HTTP相关知识。

            HTTP 请求头中verb(行为)可以用HEADER去获取大小,但是我们这里不需要去获取大小,所以不需要。

            HTTP Header中存在range关键字用于指定所请求的数据范围。格式为"Range: bytes=StartPos-EndPos\r\n"如果"EndPos"不写,则默认为接收后面所有数据。

                如果StartPos超出了范围,则会返回”416 Requested Range Not Satisfiable“。当存在Range时返回的状态码始终为206。

                另外当EndPos==StartPos时,会返回1字节数据。当EndPos>StartPos时,返回所有数据。



    History:

        time:            2012/11/26

        remarks:        test finish

        auth:            monotone

*/



#include <Windows.h>   

#include <wininet.h>   

#include <stdio.h>   

#include <string>   

#include <iostream>   

#include <tchar.h>



using namespace std;   



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



const char* STR_TEST_URL = 

    "http://dl_dir.qq.com/qqfile/qq/QQ2013/QQ2013Beta1.exe"        // 以腾讯的QQ下载作为实验,这里是我临时加上的,之前我测试过下载更大的文件,没问题。

const DWORD DWORD_MAX_CCH_OF_TEST_URL = 256;

const DWORD DWORD_MAX_CCH_OF_HOST_NAME = 128;

const DWORD DWORD_MAX_CCH_OF_URL_PATH = 256;



BOOL GetWininetLastErrorMsgA(OUT string& rStrErrorMsg)

{

    BOOL lbResult = FALSE;

    char* lscErrorMsg = NULL;

    if(0 != FormatMessageA(

        FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER,             // dwFlags

        GetModuleHandle( TEXT("wininet.dll") ),  // lpSource

        GetLastError(),                          // dwMessageId

        0,                                       // dwLanguageId

        (LPTSTR)&lscErrorMsg,                    // lpBuffer

        0,                                        // nSize

        NULL))

    {

        rStrErrorMsg = lscErrorMsg;

        lbResult = TRUE;

    }



    if(NULL != lscErrorMsg)

        LocalFree(lscErrorMsg);



    return lbResult;

}





int main()   

{   

    HINTERNET hInetOpen = NULL;

    HINTERNET hInetConnect = NULL;

    HINTERNET hInetRequest = NULL;

    HANDLE lhFile = NULL;

    do

    {

        // struct to contains the constituent parts of a URL

        URL_COMPONENTS ldCrackedURL;   

        ZeroMemory(&ldCrackedURL, sizeof(URL_COMPONENTS));   

        ldCrackedURL.dwStructSize = sizeof(URL_COMPONENTS);                    // 必须设置



        // buffer to store host name

        TCHAR szHostName[DWORD_MAX_CCH_OF_HOST_NAME] = {0};

        ldCrackedURL.lpszHostName = szHostName;   

        ldCrackedURL.dwHostNameLength = DWORD_MAX_CCH_OF_HOST_NAME;            // 字符数



        // buffer to store url path

        char szUrlPath[DWORD_MAX_CCH_OF_URL_PATH] = {0}; 

        ldCrackedURL.lpszUrlPath = szUrlPath;   

        ldCrackedURL.dwUrlPathLength  = DWORD_MAX_CCH_OF_URL_PATH;            // 字符数



        // 该函数用来将给定的Ulr分割成对应的部分。如果URL_COMPONENTS内部成员指针指向提供的缓冲,则其对应的长度也必须提供缓冲区大小。函数成功返回后,会将实际拷贝的内容大小存放在指针对象的大小中,不包括最后结束符。

        // 如果提供的URL_COMPONENTS内部各指针指向NULL,而dwStructSize成员不为0,则调用函数后,指针成员会存储对应内容的第一个字符的地址,对应长度则为该内容实际的长度。

        // 注意不要在使用"file://"类的URL时包含空格。

        if(FALSE == InternetCrackUrlA(STR_TEST_URL, (DWORD)strlen(STR_TEST_URL), 0, &ldCrackedURL))

        {

            // GetLastError();

            break;

        }



        // Get file name,注意,只适用于ulr末尾包含了文件名的url。

        string loStrFileName(ldCrackedURL.lpszUrlPath);

        string::size_type liFileNamePos = loStrFileName.rfind("/");

        if(string::npos != liFileNamePos)

        {

            loStrFileName = loStrFileName.substr(liFileNamePos + 1, string::npos);

        }



        // open internet

        hInetOpen = InternetOpenA("Breakpoint Continue Dounload Sample", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

        if(NULL == hInetOpen)

        {

            // GetLastError();

            break;

        }



        // connect server 

        hInetConnect = InternetConnectA(hInetOpen, ldCrackedURL.lpszHostName, ldCrackedURL.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);

        if(NULL == hInetConnect)

        {

            // GetLastError();

            break;

        }



        

        /* test 1:    

                    Read the destination file size as the size of data which download last(if file not exist, set the size to zero).

                    And set the value argument of HTTP Header "Range: bytes=value-\r\n" to "size - 1"(the size position).

                    Then open(or create) the file and append data from the end until no data to read, it means file download over.

        */

        

        // Get the file size

        lhFile = CreateFileA(loStrFileName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        if(lhFile ==  INVALID_HANDLE_VALUE)

        {

            break;

        }

        LARGE_INTEGER ldFileSize;

        if(FALSE == GetFileSizeEx(lhFile, &ldFileSize))

        {

            break;

        }



        // make the start position

        LONGLONG lllStartPos = 0;

        if(0 == ldFileSize.QuadPart)

        {

            cout << "new file to download. " << endl;

        }

        else

        {

            // Set the file pointer position

            if(INVALID_SET_FILE_POINTER == SetFilePointer(lhFile, 0, NULL, FILE_END))

            {

                cout << "Move file pointer failed. " << endl;



                break;

            }



            lllStartPos = ldFileSize.QuadPart;

            cout << "continue to download from position:" << lllStartPos << endl;

        }



        // convert the range start position to character

        char lscRangeStartPosition[30] = {0};

        if(0 != _i64toa_s((__int64)(lllStartPos), lscRangeStartPosition, sizeof(lscRangeStartPosition), 10))

        {

            break;

        }



        // additional header: set the file data range .

        string loAdditionalHeader = "Range: bytes=";

        loAdditionalHeader += lscRangeStartPosition;            // start position of remaining

        loAdditionalHeader += "-\r\n";        



        // open request with "GET" verb to get the remaining file data

        const char* lplpszAcceptTypes[] = {"*/*", NULL};

        hInetRequest = HttpOpenRequestA(hInetConnect, "GET", ldCrackedURL.lpszUrlPath, "HTTP/1.1", NULL, lplpszAcceptTypes, 0, 0);

        if(NULL == hInetConnect)

        {

            // GetLastError();

            break;

        }



        // send request with additional header

        if(FALSE == HttpSendRequestA(hInetRequest, loAdditionalHeader.c_str(), loAdditionalHeader.size(), NULL, 0))

        {

            // GetLastError();

            break;

        }



        // query the status code from the reponse of servers

        DWORD ldwStatusCode;

        DWORD ldwCbOfStatusCode = sizeof(ldwStatusCode);

        if(FALSE == HttpQueryInfo(hInetRequest, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &ldwStatusCode, &ldwCbOfStatusCode, 0))

        {

            break;

        }

        



        //HTTP_QUERY_CONTENT_RANGE 

        //// get file size





        // check the status code

        if(416 == ldwStatusCode)                // 416 Requested Range Not Satisfiable

        {

            cout << "the file does not need to download." << endl;

            break;

        }

        else if(200 != ldwStatusCode && 206 != ldwStatusCode)    // 206 Partial Content

        {

            // statuscode means error occurred.

            break;

        }



        // loop to read the data from HTTP and store to file

        BYTE lpbBufferToReceiveData[2048];    // 存放读取数据的Buffer

        DWORD ldwCbBuffer = 2048;

        DWORD ldwCrtCbReaded;                // 本次实际读取的字节数

        DWORD ldwCbWritten = 0;                // 本次实际写入到文件的字节数

        bool lbIsOk = false;



        LONGLONG lllCbAllRead = 0;

        do  

        {   

            // read data

            if (FALSE == InternetReadFile(hInetRequest, lpbBufferToReceiveData, ldwCbBuffer,  &ldwCrtCbReaded))   

            {    

                cout << "read data failed." << endl;

                break;

            }



            

            if(ldwCrtCbReaded == 0)            // all data haved been read.

            {

                cout << "Congratulation! file download finish successfully." << endl;

                break;   

            }            



            // write to file

            if(FALSE == WriteFile(lhFile, lpbBufferToReceiveData, ldwCrtCbReaded, &ldwCbWritten, NULL) || ldwCbWritten != ldwCrtCbReaded) 

            { 

                cout << "A exception happens when write data to file" << endl;

                break; 

            }



            // clear data in buffer

            ZeroMemory(lpbBufferToReceiveData, ldwCrtCbReaded);



            lllCbAllRead += ldwCrtCbReaded;



            cout << "crt readed data size:——————————" << lllCbAllRead / 1048576 << "MB" << endl;



        } while (true);





    }while(false);

    string loStrErrorMsg;

    if(FALSE != GetWininetLastErrorMsgA(loStrErrorMsg))

    {

        cout << loStrErrorMsg.c_str() << endl;

    }



    if(NULL != lhFile)

    {

        CloseHandle(lhFile);

    }



    if(NULL != hInetRequest)

    {

        InternetCloseHandle(hInetRequest);

    }

    if(NULL != hInetConnect)

    {

        InternetCloseHandle(hInetConnect);

    }

    if(NULL != hInetOpen)

    {

        InternetCloseHandle(hInetOpen);

    }



    getchar();

    return 0;

}

 

不足之处:

1、没有检测服务器端到底支不支持断点续传。

2、没有检测当前网络状态。

3、对于整个流程的出错检测不是很清晰。

你可能感兴趣的:(断点续传)