• 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、对于整个流程的出错检测不是很清晰。

  • 相关阅读:
    springboot配置tomcat大全
    python 列表推导式
    python中yield的用法详解——最简单,最清晰的解释
    正则表达式
    python 装饰器
    python 接口类、抽象类、多态
    python split和os.path.split()
    pyhton 多继承的执行顺序
    python unittest 加载测试用例的方法
    python unittest中的四个概念
  • 原文地址:https://www.cnblogs.com/monotone/p/2800934.html
Copyright © 2020-2023  润新知