C# Http多线程下载、断点续传

时间:2024-10-06 07:20:05

查了下资料,Http断点续传主要是Http请求包中的Range头


多线程下载需要管理好每一个线程下载的文件段


整个代码大致如下


  1. using System;
  2. using ;
  3. using ;
  4. using ;
  5. using ;
  6. using ;
  7. using ;
  8. using ;
  9. using ;
  10. namespace FengBan
  11. {
  12. ///
  13. /// 多线程下载、断点下载的管理类
  14. ///
  15. class DFGManager : IDisposable
  16. {
  17. private string dfgFilename;
  18. private int size;
  19. private FileStream writer;
  20. private SortedSet
  21. rset = new SortedSet
  22. (new RangeCompare());
  23. ///
  24. /// 多线程下载、断点下载的管理类
  25. ///
  26. ///
  27. internal DFGManager(string dfgFilename)
  28. {
  29. this.dfgFilename = dfgFilename;
  30. if ((dfgFilename))
  31. {
  32. ReadDFGFile();
  33. (dfgFilename);
  34. }
  35. writer = new FileStream(dfgFilename, );
  36. WriteDFGFile();
  37. }
  38. ///
  39. /// 获取或设置要下载的文件大小
  40. ///
  41. public int Size
  42. {
  43. get { return size; }
  44. set
  45. {
  46. size = value;
  47. byte[] head = new byte[16];
  48. byte[] bsize = (size);
  49. int i;
  50. for (i = 0; i < (); i++)
  51. {
  52. head[i] = bsize[i];
  53. }
  54. (0, );
  55. (head, 0, 16);
  56. ();
  57. }
  58. }
  59. ///
  60. /// 释放文件段,该段已经下载完成
  61. ///
  62. ///
  63. public void Free(Range range)
  64. {
  65. lock (rset)
  66. {
  67. var find = from r in rset
  68. where == && == 1
  69. select r;
  70. if (() == 1)
  71. {
  72. var ele = ();
  73. = 2;
  74. = 2;
  75. if ( < )
  76. {
  77. = ;
  78. }
  79. else
  80. {
  81. = ;
  82. }
  83. MergeRange();
  84. ((), 0, 4);
  85. ((), 0, 4);
  86. ();
  87. }
  88. else if (() == 0)
  89. {
  90. throw new Exception("找不到释放的块");
  91. }
  92. else
  93. {
  94. throw new Exception("多个符合条件的块");
  95. }
  96. }
  97. }
  98. ///
  99. /// 分配文件段
  100. ///
  101. ///
  102. 期望分配的大小
  103. ///
  104. 实际分配的大小
  105. public Range Alloc(int expectSize = 102400)
  106. {
  107. lock (rset)
  108. {
  109. Range range = new Range();
  110. if ( == 0)
  111. {
  112. = 0;
  113. = (expectSize, this.size);
  114. = 1;
  115. (range);
  116. }
  117. else if ( == 1)
  118. {
  119. if (().left > 0)
  120. {
  121. = 0;
  122. = ().left;
  123. = 1;
  124. (range);
  125. }
  126. else if (().right < size)
  127. {
  128. = ().right;
  129. = (size, + expectSize);
  130. = 1;
  131. (range);
  132. }
  133. else
  134. {
  135. range = null;
  136. }
  137. }
  138. else
  139. {
  140. if (().left > 0)
  141. {
  142. = 0;
  143. = ().left;
  144. = 1;
  145. (range);
  146. }
  147. else if (().right < size)
  148. {
  149. = ().right;
  150. = (size, + expectSize);
  151. = 1;
  152. (range);
  153. }
  154. else
  155. {
  156. for (int i = 1; i < ; i++)
  157. {
  158. Range l = (i - 1);
  159. Range r = (i);
  160. if ( < )
  161. {
  162. = ;
  163. = ;
  164. = 1;
  165. (range);
  166. return range;
  167. }
  168. }
  169. range = null;
  170. }
  171. }
  172. return range;
  173. }
  174. }
  175. private void ReadDFGFile()
  176. {
  177. StreamReader reader = new StreamReader(dfgFilename);
  178. byte[] head = new byte[16];/*head占4个int*/
  179. (head, 0, 16);
  180. size = BitConverter.ToInt32(head, 0);
  181. byte[] buff = new byte[8];
  182. int rd = 0;
  183. do
  184. {
  185. rd = (buff, 0, 8);
  186. if (rd == 0)
  187. {
  188. break;
  189. }
  190. Range range = new Range();
  191. = BitConverter.ToInt32(buff, 0);
  192. = BitConverter.ToInt32(buff, 4);
  193. = 2;
  194. (range);
  195. }
  196. while (true);
  197. ();
  198. MergeRange();
  199. }
  200. private void WriteDFGFile()
  201. {
  202. byte[] head = new byte[16];
  203. byte[] bsize = (size);
  204. int i;
  205. for (i = 0; i < (); i++)
  206. {
  207. head[i] = bsize[i];
  208. }
  209. (head, 0, 16);
  210. for (i = 0; i < ; i++)
  211. {
  212. Range r = (i);
  213. if ( == 2)
  214. {
  215. ((), 0, 4);
  216. ((), 0, 4);
  217. }
  218. }
  219. ();
  220. }
  221. private void MergeRange()
  222. {
  223. int i;
  224. if ( >= 2)
  225. {
  226. Range last = ();
  227. for (i = 1; i < && >= 2; i++)
  228. {
  229. Range cur = (i);
  230. if ( <= && == 2 && == 2)
  231. {
  232. = ;
  233. (cur);
  234. i--;
  235. }
  236. else
  237. {
  238. last = cur;
  239. }
  240. }
  241. }
  242. }
  243. ///
  244. /// 释放资源
  245. ///
  246. public void Dispose()
  247. {
  248. if (writer != null)
  249. {
  250. ();
  251. writer = null;
  252. }
  253. }
  254. ~DFGManager()
  255. {
  256. Dispose();
  257. }
  258. }
  259. class Range
  260. {
  261. public int left;
  262. public int right;
  263. public int status;
  264. }
  265. class RangeCompare : IComparer
  266. {
  267. public int Compare(Range x, Range y)
  268. {
  269. return ( - );
  270. }
  271. }
  272. }
  273. using System;
  274. using ;
  275. using ;
  276. using ;
  277. using ;
  278. using ;
  279. using ;
  280. using ;
  281. using ;
  282. using ;
  283. namespace FengBan
  284. {
  285. class Program
  286. {
  287. static void Main()
  288. {
  289. MultiThreadDownload downloader = new MultiThreadDownload();
  290. ("/sw-search-sp/soft/3a/12350/QQ_V7.0.14275.0_setup.", "");
  291. }
  292. }
  293. ///
  294. /// 多线程下载类
  295. ///
  296. class MultiThreadDownload
  297. {
  298. /*dfg文件记录下载好的文件段*/
  299. private const string suffix = ".dfg";
  300. private string site;/*文件下载地址*/
  301. private string outname;/*下载的文件名称*/
  302. private DFGManager dfgManager;/*文件分段下载管理对象*/
  303. private FileStream writer;/*多个下载子线程共用一个输出流*/
  304. /*通过currentDownload和lastDownload来实现限速*/
  305. private int currentDownload;
  306. private int lastDownload;
  307. private int shouldSleep;/*限速时应该休眠的时长,单位毫秒*/
  308. private timer;/*限速计时器*/
  309. /*上网代理*/
  310. public string ProxyAddressAndPort { get; set; }
  311. public string ProxyUserName { get; set; }
  312. public string ProxyPassword { get; set; }
  313. public MultiThreadDownload(int ThreadCount = 5)
  314. {
  315. this.ThreadCount = ThreadCount;
  316. timer = new (1000);
  317. += CheckDownloadSpeed;
  318. = true;
  319. }
  320. ///
  321. /// 下载文件的函数,仅支持http协议下载
  322. ///
  323. ///
  324. 文件下载地址
  325. ///
  326. 输出文件名
  327. public void Download(string site, string outname)
  328. {
  329. string realname = outname;
  330. outname += ".down";
  331. this.site = site;
  332. this.outname = outname;
  333. try
  334. {
  335. /*根据需要设置代理*/
  336. if (ProxyEnable)
  337. {
  338. ICredentials cred = new NetworkCredential(ProxyUserName, ProxyPassword);
  339. WebProxy p = new WebProxy(ProxyAddressAndPort, true, null, cred);
  340. = p;
  341. }
  342. /*获取要下载的文件的信息,主要是获取文件大小*/
  343. HttpWebRequest request = (HttpWebRequest)(site);
  344. = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36";
  345. WebResponse response = ();
  346. Stream responseStream = ();
  347. long contentLength = ;
  348. ("要下载的文件大小:{0}", contentLength);
  349. ();
  350. ();
  351. /*创建管理对象,负责给每一个线程分配下载的文件段*/
  352. dfgManager = new DFGManager(outname + suffix);
  353. = Convert.ToInt32(contentLength);
  354. currentDownload = 0;
  355. lastDownload = 0;
  356. writer = new FileStream(outname, );
  357. ManualResetEvent[] _ManualEvents = new ManualResetEvent[ThreadCount];
  358. //开启线程池下载
  359. for (int i = 0; i < ThreadCount; i++)
  360. {
  361. _ManualEvents[i] = new ManualResetEvent(false);
  362. (new WaitCallback(SDown), _ManualEvents[i]);
  363. }
  364. (_ManualEvents);/*等待子线程下载结束*/
  365. ("全部下载子线程结束");
  366. //下载完成,释放资源
  367. ();
  368. ();
  369. /*收尾*/
  370. if ((realname))
  371. {
  372. (realname);
  373. }
  374. (outname, realname);
  375. (outname + suffix);
  376. }
  377. finally
  378. {
  379. }
  380. }
  381. private void CheckDownloadSpeed(object sender, e)
  382. {
  383. int curSpeed = (currentDownload - lastDownload);
  384. ("当前速度 {0:f} kb/s", ((double)curSpeed) / 1000);
  385. lastDownload = currentDownload;
  386. //如果当前速度超过设定的速度,则应该执行限速动作
  387. //未实现
  388. }
  389. ///
  390. /// 设置代理
  391. ///
  392. ///
  393. 代理服务器地址和端口
  394. ///
  395. 代理服务器用户名
  396. ///
  397. 代理服务器密码
  398. public void SetProxy(string ProxyAddressAndPort, string Username, string Password)
  399. {
  400. this.ProxyAddressAndPort = ProxyAddressAndPort;
  401. this.ProxyUserName = Username;
  402. this.ProxyPassword = Password;
  403. this.ProxyEnable = true;
  404. }
  405. /*是否使用代理*/
  406. public bool ProxyEnable
  407. {
  408. get;
  409. set;
  410. }
  411. ///
  412. /// 设置或获取多线程下载的数量
  413. ///
  414. public int ThreadCount
  415. {
  416. set;
  417. get;
  418. }
  419. /*多线程下载的线程函数*/
  420. private void SDown(object param)
  421. {
  422. Range range;
  423. byte[] buff = null;
  424. /*从dfgManager中获取要下载的文件段*/
  425. while ((range = ()) != null)
  426. {
  427. HttpWebRequest request = (HttpWebRequest)(site);
  428. (, );
  429. = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36";
  430. WebResponse response = ();
  431. int contentLength = (int);
  432. //分配下载的缓存空间
  433. if (buff == null || () < contentLength)
  434. {
  435. buff = new byte[contentLength];
  436. }
  437. //使用while循环读取全部字节到buff中
  438. int count = 0;
  439. while (count < contentLength)
  440. {
  441. count += ().Read(buff, count, (int)contentLength - count);
  442. }
  443. //真正从服务器下载下来的文件段
  444. Range realRange = new Range();
  445. = ;
  446. = ( + count, );
  447. (realRange);
  448. ();
  449. //将buff写入文件
  450. (, );
  451. (buff, 0, - );
  452. ();
  453. int realDown = - ;
  454. currentDownload += realDown;
  455. ("本次分段下载大小{0}", realDown);
  456. if (LimitSpeed > 0 && shouldSleep > 10)
  457. {
  458. ("休眠{0}", shouldSleep);
  459. (shouldSleep);
  460. }
  461. }
  462. ("线程执行结束");
  463. /*通知主线程执行完成*/
  464. ManualResetEvent e = (ManualResetEvent)param;
  465. ();
  466. }
  467. private int xiansu;
  468. ///
  469. /// 表示是否限速,0表示不限速,单位 b/s
  470. ///
  471. public int LimitSpeed
  472. {
  473. get
  474. {
  475. return xiansu;
  476. }
  477. set
  478. {
  479. xiansu = value;
  480. if (value > 0)
  481. {
  482. = true;
  483. }
  484. }
  485. }
  486. }
  487. }