织梦CMS - 轻松建站从此开始!

罗索

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

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

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

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


GET /Path/FileName HTTP/1.0
Host: www.server.com:80
Accept: */*
User-Agent: GeneralDownloadApplication
Connection: close

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

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

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

关于HTTP协议更多的细节可以参考RFC2616(HTTP 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字段,那么服务器会返回206(Partial Content),同时HTTP头中也会有一个相应的Content-Range字段,类似下面的格式:

Content-Range: bytes 500-999/1000

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

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

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

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

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

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

     接口是这样设计的

  1. #ifndef __HTTP_DOWNLOAD_FILE_H__   
  2. #define __HTTP_DOWNLOAD_FILE_H__   
  3.  
  4. #ifdef HTTPDOWNLOADFILE_EXPORTS   
  5. #define HTTPDOWNLOADFILE_API __declspec(dllexport)   
  6. #else   
  7. #define HTTPDOWNLOADFILE_API /*__declspec(dllimport)*/   
  8. #endif   
  9.  
  10. #ifdef __cplusplus   
  11. extern "C" {   
  12. #endif   
  13.  
  14. // HTTP Download File Error Codes   
  15. #define HTTPDF_OK  0x00000000   
  16. #define HTTPDF_ERROR_SOCKET 0x00000001   
  17. #define HTTPDF_ERROR_URL     0x00000002   
  18. #define HTTPDF_ERROR_CONNECT 0x00000003   
  19. #define HTTPDF_ERROR_REQUEST 0x00000004   
  20. #define HTTPDF_ERROR_FILE_IO 0x00000005   
  21. #define HTTPDF_ERROR_TIMEOUT 0x00000006   
  22. #define HTTPDF_ERROR_HTTP    0x00000007   
  23. #define HTTPDF_ERROR_BUFFER_SIZE  0x00000008   
  24. #define HTTPDF_ERROR_USER_CANCELED 0x00000009   
  25. #define HTTPDF_ERROR_INVALID_PARAMETER 0x0000000A   
  26.  
  27. // HTTP Download File Flags   
  28. #define HTTPDF_FLAG_HTTP_ERROR_PAGE 0x00000001   
  29. #define HTTPDF_FLAG_CALLBACK        0x00000002   
  30. #define HTTPDF_FLAG_GET_FILE_SIZE_ONLY 0x00000004   
  31. #define HTTPDF_FLAG_DELETE_INVALID_FILE 0x00000008   
  32. #define HTTPDF_FLAG_RESUME_POSITION 0x00000010   
  33. #define HTTPDF_FLAG_END_POSITION    0x00000020   
  34. #define HTTPDF_FLAG_TIMEOUT         0x00000040   
  35.  
  36. // HTTP Download File Status Codes   
  37. #define HTTPDF_STATUS_CONNECTING    0x00000001   
  38. #define HTTPDF_STATUS_DOWNLOADING   0x00000002   
  39.  
  40. // HTTP Response Status Codes (from wininet.h)   
  41. #define HTTP_STATUS_CONTINUE   100
  42. // OK to continue with request   
  43. #define HTTP_STATUS_SWITCH_PROTOCOLS    101
  44. // server has switched protocols in upgrade header   
  45.  
  46. #define HTTP_STATUS_OK                  200
  47. // request completed
  48. #define HTTP_STATUS_CREATED             201
  49. // object created, reason = new URI   
  50. #define HTTP_STATUS_ACCEPTED            202
  51. // async completion (TBS)   
  52. #define HTTP_STATUS_PARTIAL             203
  53. // partial completion   
  54. #define HTTP_STATUS_NO_CONTENT          204
  55. // no info to return   
  56. #define HTTP_STATUS_RESET_CONTENT       205
  57. // request completed, but clear form   
  58. #define HTTP_STATUS_PARTIAL_CONTENT     206
  59. // partial GET furfilled   
  60.  
  61. #define HTTP_STATUS_AMBIGUOUS           300
  62. // server couldn't decide what to return   
  63. #define HTTP_STATUS_MOVED               301
  64. // object permanently moved   
  65. #define HTTP_STATUS_REDIRECT            302
  66. // object temporarily moved   
  67. #define HTTP_STATUS_REDIRECT_METHOD     303
  68. // redirection w/ new access method   
  69. #define HTTP_STATUS_NOT_MODIFIED        304
  70. // if-modified-since was not modified   
  71. #define HTTP_STATUS_USE_PROXY           305
  72. // redirection to proxy, location header specifies proxy to use   
  73. #define HTTP_STATUS_REDIRECT_KEEP_VERB  307
  74. // HTTP/1.1: keep same verb   
  75.  
  76. #define HTTP_STATUS_BAD_REQUEST         400 // invalid syntax   
  77. #define HTTP_STATUS_DENIED              401 // access denied   
  78. #define HTTP_STATUS_PAYMENT_REQ         402
  79. // payment required   
  80. #define HTTP_STATUS_FORBIDDEN           403
  81. // request forbidden   
  82. #define HTTP_STATUS_NOT_FOUND           404
  83. // object not found   
  84. #define HTTP_STATUS_BAD_METHOD          405
  85. // method is not allowed   
  86. #define HTTP_STATUS_NONE_ACCEPTABLE     406
  87. // no response acceptable to client found   
  88. #define HTTP_STATUS_PROXY_AUTH_REQ      407
  89. // proxy authentication required   
  90. #define HTTP_STATUS_REQUEST_TIMEOUT     408
  91. // server timed out waiting for request   
  92. #define HTTP_STATUS_CONFLICT            409
  93. // user should resubmit with more info   
  94. #define HTTP_STATUS_GONE                410
  95. // the resource is no longer available   
  96. #define HTTP_STATUS_LENGTH_REQUIRED     411
  97. // the server refused to accept request w/o a length   
  98. #define HTTP_STATUS_PRECOND_FAILED      412
  99. // precondition given in request failed   
  100. #define HTTP_STATUS_REQUEST_TOO_LARGE   413
  101. // request entity was too large   
  102. #define HTTP_STATUS_URI_TOO_LONG        414
  103. // request URI too long   
  104. #define HTTP_STATUS_UNSUPPORTED_MEDIA   415
  105. // unsupported media type   
  106. #define HTTP_STATUS_RETRY_WITH          449
  107. // retry after doing the appropriate action.   
  108. #define HTTP_STATUS_SERVER_ERROR        500 // internal server error   
  109. #define HTTP_STATUS_NOT_SUPPORTED       501 // required not supported   
  110. #define HTTP_STATUS_BAD_GATEWAY         502
  111. // error response received from gateway   
  112. #define HTTP_STATUS_SERVICE_UNAVAIL     503 // temporarily overloaded   
  113. #define HTTP_STATUS_GATEWAY_TIMEOUT     504 // timed out waiting for gateway   
  114. #define HTTP_STATUS_VERSION_NOT_SUP     505 // HTTP version not supported   
  115.  
  116. typedef struct tagDOWNLOADFILEPROGRESS   
  117. {   
  118.      DWORD         dwStatus;   
  119.      DWORD         dwHTTPStatusCode;   
  120.      LPCTSTR       lpszHostName;   
  121.      LPCTSTR       lpszFileRealURL;   
  122.      WORD          nPort;   
  123.      LPCTSTR       lpszFileName;   
  124.      DWORD         dwFileSize;   
  125.      DWORD         dwBytesWritten;   
  126.      DWORD         dwContentLength;   
  127.      DWORD         dwResumePos;   
  128.      DWORD         dwTimeElapsed;   
  129.      LPVOID        lpParameter;   
  130. } DOWNLOADFILEPROGRESS, *LPDOWNLOADFILEPROGRESS;   
  131.  
  132. typedef BOOL (CALLBACK* LPFNHTTPDOWNLOADFILE)(LPDOWNLOADFILEPROGRESS lpProgress);   
  133. typedef struct tagHTTPDOWNLOADFILEINFO   
  134. {   
  135.      DWORD         cbSize;   
  136.      DWORD         dwFlags;   
  137.      LPCTSTR       lpszFileURL;   
  138.      LPTSTR        lpszFileSavePath;   
  139.      DWORD         dwPathLen;   
  140.      LPCTSTR       lpszFileErrorPage;   
  141.      DWORD         dwResumePos;   
  142.      DWORD         dwEndPos;   
  143.      DWORD         dwTimeOut;   
  144.      DWORD         dwHTTPStatusCode;   
  145.      DWORD         dwFileSize;   
  146.      DWORD         dwBytesWritten;   
  147.      DWORD         dwError;   
  148.      LPVOID        lpParameter;   
  149.      LPVOID        lpReserved;   
  150.      LPFNHTTPDOWNLOADFILE pfnCallback;   
  151. } HTTPDOWNLOADFILEINFO, *LPHTTPDOWNLOADFILEINFO;   
  152. HTTPDOWNLOADFILE_API BOOL HTTPDownloadFile(LPHTTPDOWNLOADFILEINFO lpDownloadFileInfo);   
  153. #ifdef __cplusplus  
  154. }   
  155. #endif   
  156. #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成员有效。此时dwResumePos和dwEndPos共同构成了一个区间,这个区间指明了要下载文件的某一部分。这样可以使多个线程同时下载文件的各个部分。只有设置了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_POSITION和HTTPDF_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

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

如果请求下载整个文件,则dwContentLength与dwFileSize相同,否则会受到dwResumePos和dwEndPos的影响。


dwResumePos

         同HTTPDOWNLOADFILEINFO结构的dwResumePos成员。


dwTimeElapsed

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

与dwBytesWritten结合可以统计BPS。


lpParameter

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


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

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

  1.      // 创建文件并将接收到的文件内容保存到本地  
  2.      m_hFile = CreateFile(m_szFileName, GENERIC_WRITE,  
  3. FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, NULL, NULL);  
  4.      if (m_hFile == INVALID_HANDLE_VALUE)  
  5.      {  
  6.          m_pDownloadFileInfo->dwError = HTTPDF_ERROR_FILE_IO;  
  7.          return FALSE;  
  8.      } 

http://p.blog.csdn.net/images/p_blog_csdn_net/yangdelong/EntryImages/
20090925/HTTPDownloadFile.zip.jpg

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

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

  1. HANDLE hEvent = NULL;  
  2. BOOL WINAPI DownloadProgress(LPDOWNLOADFILEPROGRESS lpProgress)   
  3. {  
  4.      // 显示文件下载进度  
  5.      if (lpProgress->dwStatus == HTTPDF_STATUS_CONNECTING)  
  6.      {  
  7.          _tprintf(_T("Thread %d Status: Connecting...\n"),
  8. lpProgress->lpParameter);  
  9.          _tprintf(_T("URL: %s\n"),
  10. lpProgress->lpszFileRealURL);  
  11.          _tprintf(_T("Host: %s\tPort: %d\n"),
  12. lpProgress->lpszHostName, lpProgress->nPort);  
  13.          _tprintf(_T("Parameter: %d\n"),
  14. (DWORD)lpProgress->lpParameter);  
  15.      }  
  16.      else if (lpProgress->dwStatus == HTTPDF_STATUS_DOWNLOADING)  
  17.      {  
  18.          _tprintf(_T("Thread %d Status: Downloading...\n"),
  19. lpProgress->lpParameter);  
  20.          _tprintf(_T("HTTP Status Code: %d\n"),
  21. lpProgress->dwHTTPStatusCode);  
  22.          _tprintf(_T("FileName: %s\tFileSize: %d\n"),
  23. lpProgress->lpszFileName, lpProgress->dwFileSize);  
  24.          _tprintf(_T("ResumePos: %d\t\tContentLength: %d\n"),
  25. lpProgress->dwResumePos, lpProgress->dwContentLength);  
  26.          _tprintf(_T("Time Elapsed: %d\t\tBytes: %d\n"), 
  27. lpProgress->dwTimeElapsed, lpProgress->dwBytesWritten);  
  28.          _tprintf(_T("Average BPS: %d B/S\n"), 
  29. lpProgress->dwBytesWritten / (lpProgress->dwTimeElapsed / 1000 + 1));  
  30.      }  
  31.      _tprintf(_T("\n"));  
  32.      return TRUE;  
  33. }  
  34.  
  35. DWORD WINAPI DownloadThread(LPVOID lpParameter)  
  36. {  
  37.      HTTPDOWNLOADFILEINFO stDownloadFileInfo = *(HTTPDOWNLOADFILEINFO *)lpParameter;  
  38.      // 通知主线程: 已完成参数复制, 可以启动其它线程  
  39.      SetEvent(hEvent);  
  40.      // 下载文件, 出错后在断点处继续下载, 最多重试5次  
  41.      DWORD dwTime = GetTickCount();  
  42.      BOOL bResult = FALSE;  
  43.      for (int i = 0; i < 5 && !bResult; i++)  
  44.      {  
  45.          stDownloadFileInfo.dwResumePos += stDownloadFileInfo.dwBytesWritten;  
  46.          bResult = HTTPDownloadFile(&stDownloadFileInfo);  
  47.      }  
  48.  
  49.      TCHAR szMessage[1024] = {0};   
  50.      _stprintf(szMessage, 
  51. _T("线程 %d 从 %d 到 %d.\n写入 %d 字节.\n错误: %d.\n重试: %d.\n耗时: %d ms."),   
  52. stDownloadFileInfo.lpParameter,   
  53. stDownloadFileInfo.dwResumePos,   
  54. stDownloadFileInfo.dwEndPos,   
  55. stDownloadFileInfo.dwBytesWritten,   
  56. stDownloadFileInfo.dwError,   
  57. i - 1,   
  58. GetTickCount() - dwTime);   
  59. MessageBox(NULL, szMessage, _T("完成"), MB_OK);   
  60.  
  61.      if (!bResult)   
  62.     {   
  63. return -1;   
  64.     }  
  65.      return 0;  
  66. }   
  67.  
  68.  
  69. int main(int argc, char* argv[])  
  70. {  
  71.      // 获得服务器上的文件大小   
  72.      TCHAR szFileURL[] = _T("http://localhost/download/wmp9.exe");   
  73.      TCHAR szFileSavePath[MAX_PATH] = _T("c:\\");   
  74.      HTTPDOWNLOADFILEINFO stDownloadFileInfo;   
  75.      ZeroMemory(&stDownloadFileInfo, sizeof(stDownloadFileInfo));   
  76.      stDownloadFileInfo.cbSize = sizeof(stDownloadFileInfo);   
  77.      stDownloadFileInfo.dwFlags =
  78. HTTPDF_FLAG_GET_FILE_SIZE_ONLY | HTTPDF_FLAG_TIMEOUT;   
  79.      stDownloadFileInfo.dwTimeOut = 30 * 1000;   
  80.      stDownloadFileInfo.lpszFileURL = szFileURL;   
  81.      stDownloadFileInfo.lpszFileSavePath = szFileSavePath;   
  82.      stDownloadFileInfo.dwPathLen = sizeof(szFileSavePath) / sizeof(TCHAR);   
  83.      BOOL bResult = HTTPDownloadFile(&stDownloadFileInfo);   
  84.      if (!bResult)   
  85.      {   
  86.          _tprintf(_T("Get file size error: %d\n"), stDownloadFileInfo.dwError);   
  87.          return -1;   
  88.      }   
  89.      // 文件缓冲   
  90.      HANDLE hFile = CreateFile(szFileSavePath,
  91. GENERIC_WRITE, NULL, NULL, OPEN_ALWAYS, NULL, NULL);   
  92.      if (hFile == INVALID_HANDLE_VALUE)   
  93.      {   
  94.          _tprintf(_T("File Error.\n"));   
  95.          return -1;   
  96.      }  
  97.      SetFilePointer(hFile,stDownloadFileInfo.dwFileSize, 0, FILE_BEGIN);  
  98.      if (!SetEndOfFile(hFile))  
  99.      {  
  100.          _tprintf(_T("File Error.\n"));  
  101.          return -1;  
  102.      }  
  103.      CloseHandle(hFile);  
  104.      const int nSegmentCount = 1;  
  105.      HANDLE hThread[nSegmentCount] = {0};  
  106.      DWORD dwSegmentSize =
  107. (stDownloadFileInfo.dwFileSize / (sizeof(hThread) / sizeof(hThread[0])));  
  108.      stDownloadFileInfo.dwFlags &= ~HTTPDF_FLAG_GET_FILE_SIZE_ONLY;  
  109.      stDownloadFileInfo.dwFlags |= HTTPDF_FLAG_RESUME_POSITION
  110. | HTTPDF_FLAG_END_POSITION | HTTPDF_FLAG_CALLBACK;  
  111.      stDownloadFileInfo.pfnCallback = DownloadProgress;  
  112.      // 多线程下载  
  113.      DWORD dwTime = GetTickCount();  
  114.      hEvent = CreateEvent(NULL, FALSE, FALSE, _T("ThreadReady"));  
  115.      for (int i = 0; i < sizeof(hThread) / sizeof(hThread[0]); i++)  
  116.      {  
  117.          stDownloadFileInfo.lpParameter = (LPVOID)(i + 1);  
  118.          stDownloadFileInfo.dwResumePos = i * dwSegmentSize;  
  119.          if (i == sizeof(hThread) / sizeof(hThread[0]) - 1)  
  120.          {  
  121.               stDownloadFileInfo.dwEndPos = stDownloadFileInfo.dwFileSize - 1;  
  122.          }  
  123.          else 
  124.          {  
  125.               stDownloadFileInfo.dwEndPos = 
  126. stDownloadFileInfo.dwResumePos + dwSegmentSize - 1;  
  127.          }  
  128.          hThread[i] = (HANDLE)_beginthreadex(NULL,  
  129. 0,   
  130. (unsigned int (__stdcall *)(void *))DownloadThread,&stDownloadFileInfo,  
  131. 0,  
  132. NULL);  
  133.          WaitForSingleObject(hEvent, INFINITE);  
  134.      }  
  135.      CloseHandle(hEvent);  
  136.      // 等待所有下载线程结束  
  137.      WaitForMultipleObjects(sizeof(hThread)/sizeof(hThread[0]),hThread,TRUE,INFINITE);
  138.      for (int j = 0; j < sizeof(hThread) / sizeof(hThread[0]); j++)  
  139.      {  
  140.          if (hThread[j])  
  141.          {  
  142.               DWORD dwExitCode = 0;  
  143.               GetExitCodeThread(hThread[j], &dwExitCode);  
  144.               _tprintf(_T("ExitCode of Thread %d: %d\n"), j + 1, dwExitCode);  
  145.               CloseHandle(hThread[j]);  
  146.          }  
  147.      }  
  148.      _tprintf(_T("Download Finished: %d ms\n"), GetTickCount() - dwTime);   
  149.      return 0;   

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

http://download.csdn.net/source/1079082
 

*  转载请通知作者并注明出处,CSDN欢迎您!   *
*  作者:卢培培(goodname008)              *
*  邮箱:goodname008@163.com                *
*  专栏:http://blog.csdn.net/goodname008   *
来自:http://blog.csdn.net/goodname008/archive/2006/01/02/568668.aspx

(卢培培)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201006/9570.html]
本文出处:CSDN博客 作者:卢培培
顶一下
(1)
33.3%
踩一下
(2)
66.7%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容