利用HTTP协议实现文件下载的多线程断点续传

    技术2022-05-11  114

     

         最近研究了一下关于文件下载的相关内容,觉得还是写些东西记下来比较好。起初只是想研究研究,但后来发现写个可重用性比较高的模块还是很有必要的,我想这也是大多数开发人员的习惯吧。

     

         对于HTTP协议,向服务器请求某个文件时,只要发送类似如下的请求即可:

     

     

         GET /Path/FileName HTTP/1.0

     

    Host: www.server.com:80

     

    Accept: */*

     

    User-Agent: GeneralDownloadApplication

     

    Connection: close

     

     

         每行用一个“回车换行”分隔,末尾再追加一个“回车换行”作为整个请求的结束。

     

    第一行中的GETHTTP协议支持的方法之一,方法名是大小写敏感的,HTTP协议还支持OPTIONSHAEDPOSTPUTDELETETRACECONNECT等方法,而GETHEAD这两个方法通常被认为是“安全的”,也就是说任何实现了HTTP协议的服务器程序都会实现这两个方法。对于文件下载功能,GET足矣。GET后面是一个空格,其后紧跟的是要下载的文件从WEB服务器根开始的绝对路径。该路径后又有一个空格,然后是协议名称及协议版本。

     

    除第一行以外,其余行都是HTTP头的字段部分。Host字段表示主机名和端口号,如果端口号是默认的80则可以不写。Accept字段中的*/*表示接收任何类型的数据。User-Agent表示用户代理,这个字段可有可无,但强烈建议加上,因为它是服务器统计、追踪以及识别客户端的依据。Connection字段中的close表示使用非持久连接。

     

    关于HTTP协议更多的细节可以参考RFC2616HTTP 1.1)。因为我只是想通过HTTP协议实现文件下载,所以也只看了一部分,并没有看全。

     

    如果服务器成功收到该请求,并且没有出现任何错误,则会返回类似下面的数据:

     

     

    HTTP/1.0 200 OK

     

    Content-Length: 13057672

     

    Content-Type: application/octet-stream

     

    Last-Modified: Wed, 10 Oct 2005 00:56:34 GMT

     

    Accept-Ranges: bytes

     

    ETag: "2f38a6cac7cec51:160c"

     

    Server: Microsoft-IIS/6.0

     

    X-Powered-By: ASP.NET

     

    Date: Wed, 16 Nov 2005 01:57:54 GMT

     

    Connection: close

     

     

    不用逐一解释,很多东西一看几乎就明白了,只说我们大家都关心内容吧。

     

    第一行是协议名称及版本号,空格后面会有一个三位数的数字,是HTTP协议的响应状态码,200表示成功,OK是对状态码的简短文字描述。状态码共有5类:1xx属于通知类;2xx属于成功类;3xx属于重定向类;4xx属于客户端错误类;5xx属于服务端错误类。对于状态码,相信大家对404应该很熟悉,如果向一个服务器请求一个不存在的文件,就会得到该错误,通常浏览器也会显示类似“HTTP 404 - 未找到文件”这样的错误。Content-Length字段是一个比较重要的字段,它标明了服务器返回数据的长度,这个长度是不包含HTTP头长度的。换句话说,我们的请求中并没有Range字段(后面会说到),表示我们请求的是整个文件,所以Content-Length就是整个文件的大小。其余各字段是一些关于文件和服务器的属性信息。

     

    这段返回数据同样是以最后一行的结束标志(回车换行)和一个额外的回车换行作为结束,即“/r/n/r/n”。而“/r/n/r/n”后面紧接的就是文件的内容了,这样我们就可以找到“/r/n/r/n”,并从它后面的第一个字节开始,源源不断的读取,再写到文件中了。

     

    以上就是通过HTTP协议实现文件下载的全过程。但还不能实现断点续传,而实际上断点续传的实现非常简单,只要在请求中加一个Range字段就可以了。

     

    假如一个文件有1000个字节,那么其范围就是0-999,则:

     

     

    Range: bytes=500-      表示读取该文件的500-999字节,共500字节。

     

         Range: bytes=500-599   表示读取该文件的500-599字节,共100字节。

     

     

         Range还有其它几种写法,但上面这两种是最常用的,对于断点续传也足矣了。如果HTTP请求中包含Range字段,那么服务器会返回206Partial Content),同时HTTP头中也会有一个相应的Content-Range字段,类似下面的格式:

     

     

         Content-Range: bytes 500-999/1000

     

     

    Content-Range字段说明服务器返回了文件的某个范围及文件的总长度。这时Content-Length字段就不是整个文件的大小了,而是对应文件这个范围的字节数,这一点一定要注意。

     

    一切好像基本上没有什么问题了,本来我也是这么认为的,但事实并非如此。如果我们请求的文件的URL是类似http://www.server.com/filename.exe这样的文件,则不会有问题。但是很多软件下载网站的文件下载链接都是通过程序重定向的,比如pchomeACDSeeHTTP下载地址是:

     

    http://download.pchome.net/php/tdownload2.php?sid=5547&url=/multimedia/viewer/acdc31sr1b051007.exe&svr=1&typ=0

     

    这种地址并没有直接标识文件的位置,而是通过程序进行了重定向。如果向服务器请求这样的URL,服务器就会返回302Moved Temporarily),意思就是需要重定向,同时在HTTP头中会包含一个Location字段,Location字段的值就是重定向后的目的URL。这时就需要断开当前的连接,而向这个重定向后的服务器发请求。

     

         好了,原理基本上就是这些了。其实装个Sniffer好好分析一下,很容易就可以分析出来的。不过NetAnts也帮了我一些忙,它的文件下载日志对开发人员还是很有帮助的。

     

    我在写这段程序时,一开始也仅仅是实现了文件下载的功能,后来又考虑到回调、HTTP重定向、多线程断点续传、统计BPS、允许获得HTML错误页等功能,代码越写越长,不过还好,不是很恐怖,但要全部贴出来也不合适,所以只拣了些关键的。

     

         接口是这样设计的:

     

     

     

    #ifndef __HTTP_DOWNLOAD_FILE_H__

     

    #define __HTTP_DOWNLOAD_FILE_H__

     

     

    #ifdef HTTPDOWNLOADFILE_EXPORTS

     

    #define HTTPDOWNLOADFILE_API __declspec(dllexport)

     

    #else

     

    #define HTTPDOWNLOADFILE_API /*__declspec(dllimport)*/

     

    #endif

     

     

    #ifdef __cplusplus

     

    extern "C" {

     

    #endif

     

     

    // HTTP Download File Error Codes

     

    #define HTTPDF_OK                                  0x00000000

     

    #define HTTPDF_ERROR_SOCKET                        0x00000001

     

    #define HTTPDF_ERROR_URL                           0x00000002

     

    #define HTTPDF_ERROR_CONNECT                       0x00000003

     

    #define HTTPDF_ERROR_REQUEST                       0x00000004

     

    #define HTTPDF_ERROR_FILE_IO                       0x00000005

     

    #define HTTPDF_ERROR_TIMEOUT                       0x00000006

     

    #define HTTPDF_ERROR_HTTP                          0x00000007

     

    #define HTTPDF_ERROR_BUFFER_SIZE                    0x00000008

     

    #define HTTPDF_ERROR_USER_CANCELED                 0x00000009

     

    #define HTTPDF_ERROR_INVALID_PARAMETER             0x0000000A

     

     

    // HTTP Download File Flags

     

    #define HTTPDF_FLAG_HTTP_ERROR_PAGE                0x00000001

     

    #define HTTPDF_FLAG_CALLBACK                       0x00000002

     

    #define HTTPDF_FLAG_GET_FILE_SIZE_ONLY             0x00000004

     

    #define HTTPDF_FLAG_DELETE_INVALID_FILE            0x00000008

     

    #define HTTPDF_FLAG_RESUME_POSITION                0x00000010

     

    #define HTTPDF_FLAG_END_POSITION                    0x00000020

     

    #define HTTPDF_FLAG_TIMEOUT                        0x00000040

     

     

    // HTTP Download File Status Codes

     

    #define HTTPDF_STATUS_CONNECTING               0x00000001

     

    #define HTTPDF_STATUS_DOWNLOADING                  0x00000002

     

     

    // HTTP Response Status Codes (from wininet.h)

     

    #define HTTP_STATUS_CONTINUE            100 // OK to continue with request

     

    #define HTTP_STATUS_SWITCH_PROTOCOLS    101 // server has switched protocols in upgrade header

     

     

    #define HTTP_STATUS_OK                  200 // request completed

     

    #define HTTP_STATUS_CREATED             201 // object created, reason = new URI

     

    #define HTTP_STATUS_ACCEPTED            202 // async completion (TBS)

     

    #define HTTP_STATUS_PARTIAL             203 // partial completion

     

    #define HTTP_STATUS_NO_CONTENT          204 // no info to return

     

    #define HTTP_STATUS_RESET_CONTENT       205 // request completed, but clear form

     

    #define HTTP_STATUS_PARTIAL_CONTENT     206 // partial GET furfilled

     

     

    #define HTTP_STATUS_AMBIGUOUS           300 // server couldn't decide what to return

     

    #define HTTP_STATUS_MOVED               301 // object permanently moved

     

    #define HTTP_STATUS_REDIRECT            302 // object temporarily moved

     

    #define HTTP_STATUS_REDIRECT_METHOD     303 // redirection w/ new access method

     

    #define HTTP_STATUS_NOT_MODIFIED        304 // if-modified-since was not modified

     

    #define HTTP_STATUS_USE_PROXY           305 // redirection to proxy, location header specifies proxy to use

     

    #define HTTP_STATUS_REDIRECT_KEEP_VERB  307 // HTTP/1.1: keep same verb

     

     

    #define HTTP_STATUS_BAD_REQUEST         400 // invalid syntax

     

    #define HTTP_STATUS_DENIED              401 // access denied

     

    #define HTTP_STATUS_PAYMENT_REQ         402 // payment required

     

    #define HTTP_STATUS_FORBIDDEN           403 // request forbidden

     

    #define HTTP_STATUS_NOT_FOUND           404 // object not found

     

    #define HTTP_STATUS_BAD_METHOD          405 // method is not allowed

     

    #define HTTP_STATUS_NONE_ACCEPTABLE     406 // no response acceptable to client found

     

    #define HTTP_STATUS_PROXY_AUTH_REQ      407 // proxy authentication required

     

    #define HTTP_STATUS_REQUEST_TIMEOUT     408 // server timed out waiting for request

     

    #define HTTP_STATUS_CONFLICT            409 // user should resubmit with more info

     

    #define HTTP_STATUS_GONE                410 // the resource is no longer available

     

    #define HTTP_STATUS_LENGTH_REQUIRED     411 // the server refused to accept request w/o a length

     

    #define HTTP_STATUS_PRECOND_FAILED      412 // precondition given in request failed

     

    #define HTTP_STATUS_REQUEST_TOO_LARGE   413 // request entity was too large

     

    #define HTTP_STATUS_URI_TOO_LONG        414 // request URI too long

     

    #define HTTP_STATUS_UNSUPPORTED_MEDIA   415 // unsupported media type

     

    #define HTTP_STATUS_RETRY_WITH          449 // retry after doing the appropriate action.

     

     

    #define HTTP_STATUS_SERVER_ERROR        500 // internal server error

     

    #define HTTP_STATUS_NOT_SUPPORTED       501 // required not supported

     

    #define HTTP_STATUS_BAD_GATEWAY         502 // error response received from gateway

     

    #define HTTP_STATUS_SERVICE_UNAVAIL     503 // temporarily overloaded

     

    #define HTTP_STATUS_GATEWAY_TIMEOUT     504 // timed out waiting for gateway

     

    #define HTTP_STATUS_VERSION_NOT_SUP     505 // HTTP version not supported

     

     

    typedef struct tagDOWNLOADFILEPROGRESS

     

    {

     

         DWORD         dwStatus;

     

         DWORD         dwHTTPStatusCode;

     

         LPCTSTR       lpszHostName;

     

         LPCTSTR       lpszFileRealURL;

     

         WORD          nPort;

     

         LPCTSTR       lpszFileName;

     

         DWORD         dwFileSize;

     

         DWORD         dwBytesWritten;

     

         DWORD         dwContentLength;

     

         DWORD         dwResumePos;

     

         DWORD         dwTimeElapsed;

     

         LPVOID        lpParameter;

     

     

    } DOWNLOADFILEPROGRESS, *LPDOWNLOADFILEPROGRESS;

     

     

    typedef BOOL (CALLBACK* LPFNHTTPDOWNLOADFILE)(LPDOWNLOADFILEPROGRESS lpProgress);

     

     

    typedef struct tagHTTPDOWNLOADFILEINFO

     

    {

     

         DWORD         cbSize;

     

         DWORD         dwFlags;

     

         LPCTSTR       lpszFileURL;

     

         LPTSTR        lpszFileSavePath;

     

         DWORD         dwPathLen;

     

         LPCTSTR       lpszFileErrorPage;

     

         DWORD         dwResumePos;

     

         DWORD         dwEndPos;

     

         DWORD         dwTimeOut;

     

         DWORD         dwHTTPStatusCode;

     

         DWORD         dwFileSize;

     

         DWORD         dwBytesWritten;

     

         DWORD         dwError;

     

         LPVOID        lpParameter;

     

         LPVOID        lpReserved;

     

         LPFNHTTPDOWNLOADFILE pfnCallback;

     

        

     

    } HTTPDOWNLOADFILEINFO, *LPHTTPDOWNLOADFILEINFO;

     

     

    HTTPDOWNLOADFILE_API BOOL HTTPDownloadFile(LPHTTPDOWNLOADFILEINFO lpDownloadFileInfo);

     

     

    #ifdef __cplusplus

     

    }

     

    #endif

     

     

    #endif /* __HTTP_DOWNLOAD_FILE_H__ */

     

     

         DLL只有一个导出函数,即HTTPDownloadFile,该函数需要一个HTTPDOWNLOADFILEINFO结构的参数,结构的各个成员如下:

     

     

    cbSize

     

    该结构的大小,以字节为单位。

     

    必须正确设置该成员,否则会返回HTTPDF_ERROR_INVALID_PARAMETER错误。

     

     

    dwFlags

     

             标志位的组合,可以是如下各标志的逻辑加(|):

     

        

     

     

    含义

     

    HTTPDF_FLAG_HTTP_ERROR_PAGE

     

    接收HTTP错误页,如果指定该选项,则lpszFileErrorPage为保存错误页的路径及文件名。在下载文件过程中如果发生HTTP错误,则将服务器返回的HTTP错误页的内容写入到该文件中。

     

    HTTPDF_FLAG_CALLBACK

     

    指明pfnCallback成员有效,此时pfnCallback是指向回调函数的指针,不能为空,否则会返回HTTPDF_ERROR_INVALID_PARAMETER错误。

     

    HTTPDF_FLAG_GET_FILE_SIZE_ONLY

     

    只获得文件大小,而不下载文件。

     

    HTTPDF_FLAG_DELETE_INVALID_FILE

     

    删除未下载成功的文件。如果在下载过程中出现任何错误,则删除本地下载的不完整的文件。

     

    HTTPDF_FLAG_RESUME_POSITION

     

    指明dwResumePos成员有效,此时dwResumePos为继续下载文件的位置,即断点位置。这样将从该位置开始下载文件,直到文件结束。(断点续传)

     

    HTTPDF_FLAG_END_POSITION

     

    指明dwEndPos成员有效。此时dwResumePosdwEndPos共同构成了一个区间,这个区间指明了要下载文件的某一部分。这样可以使多个线程同时下载文件的各个部分。只有设置了HTTPDF_FLAG_RESUME_POSITION时,设置HTTPDF_FLAG_END_POSITION才有效,否则将忽略该标志及dwEndPos成员。

     

    HTTPDF_FLAG_TIMEOUT

     

    指明dwTimeOut成员有效,dwTimeOut成员为超时时间,单位为毫秒(ms)。

     

     

    lpszFileURL

     

             NULL结尾的字符串,表示要下载的文件的URL。不能为空。

     

     

        lpszFileSavePath

     

             NULL结尾的字符串,表示文件保存到本地的路径。不能为空。

     

    如果最后一个字符为“/”则将文件保存到该路径下,文件名与服务器上的文件名相同,否则将按该字符串指定的文件名保存。如果仅指定了路径,则函数返回后该缓冲区保存文件路径及文件名。如果缓冲区的大小不够保存文件路径及文件名,则函数调用失败,此时dwPathLen为需要的缓冲区大小,以字符(TCHAR)为单位。

     

     

        dwPathLen

     

             lpszFileSavePath指向的缓冲区的大小,以字符(TCHAR)为单位。

     

     

        lpszFileErrorPage

     

         NULL结尾的字符串,表示保存HTTP错误页的文件路径及文件名。设置HTTPDF_FLAG_HTTP_ERROR_PAGE时才有效。

     

     

    dwResumePos

     

             继续下载文件的位置,即断点位置。设置HTTPDF_FLAG_RESUME_POSITION时才有效。

     

     

        dwEndPos

     

    文件的结束位置。同时设置HTTPDF_FLAG_RESUME_POSITIONHTTPDF_FLAG_END_POSITION时才有效,与dwResumePos构成一个区间,表示下载文件的某一部分。

     

     

        dwTimeOut

     

             超时时间,以毫秒(ms)为单位。设置HTTPDF_FLAG_TIMEOUT时才有效。默认为60秒。

     

     

        dwHTTPStatusCode

     

             函数返回时,该成员保存HTTP响应状态码。

     

     

        dwFileSize

     

             函数返回时,该成员保存文件大小。

     

     

        dwBytesWritten

     

             函数返回时,该成员保存成功下载并写入文件的字节数。

     

     

        dwError

     

             函数返回时,如果下载失败,将该成员保存错误码。

     

     

    pfnCallback

     

         指向LPFNHTTPDOWNLOADFILE类型的函数的地址。设置HTTPDF_FLAG_CALLBACK函数时有效。

     

     

        lpParameter

     

             用户自定义函数。用于传给回调函数。

     

     

        lpReserved

     

             保留。必须为空。

     

     

    文件下载成功时,函数返回TRUE。当函数返回FALSE时,表示文件下载失败,dwError成员保存了错误码:

     

     

     

    含义

     

    HTTPDF_OK

     

    无错误,函数成功返回。

     

    HTTPDF_ERROR_SOCKET

     

    套接字(Socket)错误。

     

    HTTPDF_ERROR_URL

     

    非法的URL。或者lpszFileURL指定的文件无法使用HTTP协议下载。

     

    HTTPDF_ERROR_CONNECT

     

    删除未下载成功的文件。如果在下载过程中出现任何错误,则删除本地下载的不完整的文件。

     

    HTTPDF_ERROR_REQUEST

     

    向服务器发请求时失败。

     

    HTTPDF_ERROR_FILE_IO

     

    文件输入输出错误。

     

    HTTPDF_ERROR_TIMEOUT

     

    超时错误。

     

    HTTPDF_ERROR_HTTP

     

    HTTP错误。dwHTTPStatusCode保存具体错误码。

     

    HTTPDF_ERROR_BUFFER_SIZE

     

    缓冲区大小错误。lpszFileSavePath指向的缓冲不够保存文件路径及文件名。

     

    HTTPDF_ERROR_USER_CANCELED

     

    用户取消了下载操作。

     

    HTTPDF_ERROR_INVALID_PARAMETER

     

    无效的参数。

     

     

    pfnCallback指向LPFNHTTPDOWNLOADFILE类型的函数的地址,也就是回调函数的指针。该回调函数是这样定义的:

     

    BOOL WINAPI DownloadProgress(LPDOWNLOADFILEPROGRESS lpProgress);

     

     

         DOWNLOADFILEPROGRESS结构的成员如下:

     

     

        dwStatus

     

             当前的下载状态。

     

    HTTPDF_STATUS_CONNECTING 表示正在连接服务器。

     

    HTTPDF_STATUS_DOWNLOADING 表示正在下载文件。

     

     

    dwHTTPStatusCode

     

         HTTPDOWNLOADFILEINFO结构的dwHTTPStatusCode成员。

     

     

    lpszHostName

     

             NULL结尾的字符串,表示服务器的主机名称。

     

     

    lpszFileRealURL

     

         NULL结尾的字符串,表示文件在网络中的真实URL

     

    一般情况下和HTTPDOWNLOADFILEINFO结构的lpszFileURL相同,但当发生重定向时,该字符串为文件在网络中的真实URL

     

     

    nPort

     

             服务器端口号。通常情况下为80

     

     

    lpszFileName

     

             NULL结尾的字符串,表示文件保存到本地的路径及文件名。

     

     

    dwFileSize

     

             文件大小。以字节为单位。

     

     

    dwBytesWritten

     

             当前已经下载并成功写入到文件的字节数。

     

     

    dwContentLength

     

             请求文件的大小,以字节为单位。

     

    如果请求下载整个文件,则dwContentLengthdwFileSize相同,否则会受到dwResumePosdwEndPos的影响。

     

     

    dwResumePos

     

             HTTPDOWNLOADFILEINFO结构的dwResumePos成员。

     

     

    dwTimeElapsed

     

             从下载文件开始到当前经过的时间。以毫秒(ms)为单位。

     

    dwBytesWritten结合可以统计BPS

     

     

    lpParameter

     

             用户自定义参数。同HTTPDOWNLOADFILEINFO结构的lpParameter成员。

     

     

         HTTPDownloadFile函数主要就是按照HTTP协议,通过Windows Sockets FunctionsWindows套接字函数)直接向HTTP服务器发送请求,使得HTTP服务器与客户端进行良好的通讯,从而实现文件下载功能。

     

         其中,创建文件的代码片断如下:

     

     

         // 创建文件并将接收到的文件内容保存到本地

     

         m_hFile = CreateFile(m_szFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, NULL, NULL);

     

         if (m_hFile == INVALID_HANDLE_VALUE)

     

         {

     

             m_pDownloadFileInfo->dwError = HTTPDF_ERROR_FILE_IO;

     

             return FALSE;

     

         }

     

     

     

         正如大家看到的,文件是以FILE_SHARE_WRITE的方式打开的,这使得多个线程可以同时下载相同文件的不同部分并写入到本地。而HTTPDownloadFile函数本身是以同步方式执行的,也就是说当下载的文件稍微大一些时,程序就会阻塞在这里。之所以没有提供异步执行的选项,是因为当程序不同时,通常异步执行的处理逻辑也会不同,因此这部分最好还是交给客户程序员去做。

     

    我为这个函数写了一个简单的调用例程,是个控制台程序,界面不是很华丽,但却是一个完整的多线程断点续传的例子。例程基本上仿照了NetAnts,同时启动5个线程分别下载文件的5个不同部分,任何一个线程出错后都会进行重试并从断点处继续下载,最多重试5次。代码片断如下:

     

     

    HANDLE hEvent = NULL;

     

     

    BOOL WINAPI DownloadProgress(LPDOWNLOADFILEPROGRESS lpProgress)

     

    {

     

         // 显示文件下载进度

     

         if (lpProgress->dwStatus == HTTPDF_STATUS_CONNECTING)

     

         {

     

             _tprintf(_T("Thread %d Status: Connecting.../n"), lpProgress->lpParameter);

     

             _tprintf(_T("URL: %s/n"), lpProgress->lpszFileRealURL);

     

             _tprintf(_T("Host: %s/tPort: %d/n"), lpProgress->lpszHostName, lpProgress->nPort);

     

             _tprintf(_T("Parameter: %d/n"), (DWORD)lpProgress->lpParameter);

     

         }

     

         else if (lpProgress->dwStatus == HTTPDF_STATUS_DOWNLOADING)

     

         {

     

             _tprintf(_T("Thread %d Status: Downloading.../n"), lpProgress->lpParameter);

     

             _tprintf(_T("HTTP Status Code: %d/n"), lpProgress->dwHTTPStatusCode);

     

             _tprintf(_T("FileName: %s/tFileSize: %d/n"), lpProgress->lpszFileName, lpProgress->dwFileSize);

     

             _tprintf(_T("ResumePos: %d/t/tContentLength: %d/n"), lpProgress->dwResumePos, lpProgress->dwContentLength);

     

             _tprintf(_T("Time Elapsed: %d/t/tBytes: %d/n"), lpProgress->dwTimeElapsed, lpProgress->dwBytesWritten);

     

             _tprintf(_T("Average BPS: %d B/S/n"), lpProgress->dwBytesWritten / (lpProgress->dwTimeElapsed / 1000 + 1));

     

         }

     

     

         _tprintf(_T("/n"));

     

        

     

         return TRUE;

     

    }

     

     

    DWORD WINAPI DownloadThread(LPVOID lpParameter)

     

    {

     

         HTTPDOWNLOADFILEINFO stDownloadFileInfo = *(HTTPDOWNLOADFILEINFO *)lpParameter;

     

     

         // 通知主线程: 已完成参数复制, 可以启动其它线程

     

         SetEvent(hEvent);

     

     

         // 下载文件, 出错后在断点处继续下载, 最多重试5

     

         DWORD dwTime = GetTickCount();

     

         BOOL bResult = FALSE;

     

         for (int i = 0; i < 5 && !bResult; i++)

     

         {

     

             stDownloadFileInfo.dwResumePos += stDownloadFileInfo.dwBytesWritten;

     

             bResult = HTTPDownloadFile(&stDownloadFileInfo);

     

         }

     

        

     

         TCHAR szMessage[1024] = {0};

     

         _stprintf(szMessage, _T("线程 %d %d %d./n写入 %d 字节./n错误: %d./n重试: %d./n耗时: %d ms."),

     

                    stDownloadFileInfo.lpParameter,

     

                    stDownloadFileInfo.dwResumePos,

     

                    stDownloadFileInfo.dwEndPos,

     

                    stDownloadFileInfo.dwBytesWritten,

     

                    stDownloadFileInfo.dwError,

     

                    i - 1,

     

                    GetTickCount() - dwTime);

     

         MessageBox(NULL, szMessage, _T("完成"), MB_OK);

     

     

         if (!bResult)

     

         {

     

             return -1;

     

         }

     

     

         return 0;

     

    }

     

     

    int main(int argc, char* argv[])

     

    {

     

         // 获得服务器上的文件大小

     

         TCHAR szFileURL[] = _T("http://localhost/download/wmp9.exe");

     

         TCHAR szFileSavePath[MAX_PATH] = _T("c://");

     

         HTTPDOWNLOADFILEINFO stDownloadFileInfo;

     

         ZeroMemory(&stDownloadFileInfo, sizeof(stDownloadFileInfo));

     

         stDownloadFileInfo.cbSize = sizeof(stDownloadFileInfo);

     

         stDownloadFileInfo.dwFlags = HTTPDF_FLAG_GET_FILE_SIZE_ONLY | HTTPDF_FLAG_TIMEOUT;

     

         stDownloadFileInfo.dwTimeOut = 30 * 1000;

     

         stDownloadFileInfo.lpszFileURL = szFileURL;

     

         stDownloadFileInfo.lpszFileSavePath = szFileSavePath;

     

         stDownloadFileInfo.dwPathLen = sizeof(szFileSavePath) / sizeof(TCHAR);

     

         BOOL bResult = HTTPDownloadFile(&stDownloadFileInfo);

     

         if (!bResult)

     

         {

     

             _tprintf(_T("Get file size error: %d/n"), stDownloadFileInfo.dwError);

     

             return -1;

     

         }

     

     

         // 文件缓冲

     

         HANDLE hFile = CreateFile(szFileSavePath, GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, NULL, NULL);

     

         if (hFile == INVALID_HANDLE_VALUE)

     

         {

     

             _tprintf(_T("File Error./n"));

     

             return -1;

     

         }

     

     

         SetFilePointer(hFile, stDownloadFileInfo.dwFileSize, 0, FILE_BEGIN);

     

         if (!SetEndOfFile(hFile))

     

         {

     

             _tprintf(_T("File Error./n"));

     

             return -1;

     

         }

     

         CloseHandle(hFile);

     

     

         const int nSegmentCount = 1;

     

         HANDLE hThread[nSegmentCount] = {0};

     

         DWORD dwSegmentSize = (stDownloadFileInfo.dwFileSize / (sizeof(hThread) / sizeof(hThread[0])));

     

         stDownloadFileInfo.dwFlags &= ~HTTPDF_FLAG_GET_FILE_SIZE_ONLY;

     

         stDownloadFileInfo.dwFlags |= HTTPDF_FLAG_RESUME_POSITION | HTTPDF_FLAG_END_POSITION | HTTPDF_FLAG_CALLBACK;

     

         stDownloadFileInfo.pfnCallback = DownloadProgress;

     

     

         // 多线程下载

     

         DWORD dwTime = GetTickCount();

     

         hEvent = CreateEvent(NULL, FALSE, FALSE, _T("ThreadReady"));

     

         for (int i = 0; i < sizeof(hThread) / sizeof(hThread[0]); i++)

     

         {

     

             stDownloadFileInfo.lpParameter = (LPVOID)(i + 1);

     

             stDownloadFileInfo.dwResumePos = i * dwSegmentSize;

     

             if (i == sizeof(hThread) / sizeof(hThread[0]) - 1)

     

             {

     

                  stDownloadFileInfo.dwEndPos = stDownloadFileInfo.dwFileSize - 1;

     

             }

     

             else

     

             {

     

                  stDownloadFileInfo.dwEndPos = stDownloadFileInfo.dwResumePos + dwSegmentSize - 1;

     

             }

     

            

     

             hThread[i] = (HANDLE)_beginthreadex(NULL,

     

                                                       0,

     

                                                       (unsigned int (__stdcall *)(void *))DownloadThread,

     

                                                       &stDownloadFileInfo,

     

                                                       0,

     

                                                       NULL);

     

             WaitForSingleObject(hEvent, INFINITE);

     

         }

     

         CloseHandle(hEvent);

     

     

         // 等待所有下载线程结束

     

         WaitForMultipleObjects(sizeof(hThread) / sizeof(hThread[0]), hThread, TRUE, INFINITE);

     

         for (int j = 0; j < sizeof(hThread) / sizeof(hThread[0]); j++)

     

         {

     

             if (hThread[j])

     

             {

     

                  DWORD dwExitCode = 0;

     

                  GetExitCodeThread(hThread[j], &dwExitCode);

     

                  _tprintf(_T("ExitCode of Thread %d: %d/n"), j + 1, dwExitCode);

     

                  CloseHandle(hThread[j]);

     

             }

     

         }

     

     

         _tprintf(_T("Download Finished: %d ms/n"), GetTickCount() - dwTime);

     

     

         return 0;

     

    }

     

     

     

         Main函数中的nSegmentCount就是文件的分片数量,同时也是要启动的线程数。HTTPDownloadFile函数和调用例程的完整源代码可以在这里下载:http://download.csdn.net/source/1079082

     

     

    *-------------------------------------------*

     

    *  转载请通知作者并注明出处,欢迎您!   *

     

    *  作者:卢培培(goodname008              *

     

    *  邮箱:goodname008@163.com                *

     

    *  专栏:http://blog.csdn.net/goodname008   *

     

    *-------------------------------------------*


    最新回复(0)