HttpDownloader.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using System.IO;
  7. using System.Net;
  8. namespace HttpDownload
  9. {
  10. class HttpDownloader
  11. {
  12. const int bytebuff = 4096;
  13. const int ReadWriteTimeOut = 10 * 1000;// 在写入超时或读取超时之前的毫秒数
  14. const int TimeOutWait = 10 * 1000;// 请求超时前等待的毫秒数
  15. const int MaxTryTime = 10; // 最大重试次数
  16. int currentRetryTimes = 0;
  17. private bool finished = false;
  18. public bool Finished { get { return finished; } }
  19. private long totalRemoteFileSize = 0L; // 要下载的文件的总大小
  20. public long TotalRemoteFileSize { get { return totalRemoteFileSize; } }
  21. public long BytesWritten { get { return offset; } }
  22. string url, destPath;
  23. private long offset, timeStamp, bytesAtTimeStamp;
  24. volatile private int speed;
  25. FileStream fs;
  26. internal volatile bool running = false;
  27. public HttpDownloader(string url, string destPath, long offset = 0)
  28. {
  29. this.destPath = destPath;
  30. this.url = url;
  31. this.offset = offset;
  32. }
  33. public void DownloadFile()
  34. {
  35. if (isDownloadFinished(offset))
  36. {
  37. return;
  38. }
  39. try
  40. {
  41. for (; !finished && running;)
  42. HttpDownloadFile();
  43. }
  44. catch (Exception ex)
  45. {
  46. throw ex;
  47. }
  48. }
  49. const string errorInfo = "下载结束, 但是文件长度与下载字节数不一致!";
  50. /// <summary>
  51. /// 下载文件(同步) 支持断点续传
  52. /// </summary>
  53. private bool HttpDownloadFile()
  54. {
  55. //打开上次下载的文件
  56. //long lStartPos = 0;
  57. fs = getFileStream();
  58. HttpWebRequest request = null;
  59. WebResponse respone = null;
  60. Stream ns = null;
  61. try
  62. {
  63. request = (HttpWebRequest)WebRequest.Create(url);
  64. request.ReadWriteTimeout = ReadWriteTimeOut;
  65. request.Timeout = TimeOutWait;
  66. if (offset > 0)
  67. request.AddRange(offset);//设置Range值,断点续传
  68. //向服务器请求,获得服务器回应数据流
  69. respone = request.GetResponse();
  70. ns = respone.GetResponseStream();
  71. byte[] nbytes = new byte[bytebuff];
  72. int nReadSize = ns.Read(nbytes, 0, bytebuff);
  73. if (timeStamp == 0)
  74. {
  75. timeStamp = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds);
  76. bytesAtTimeStamp = offset;
  77. }
  78. while (nReadSize > 0 && running)
  79. {
  80. long now = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds);
  81. if (now - timeStamp >= 1000)
  82. {
  83. long dataDiff = offset - bytesAtTimeStamp, timeDiff = now - timeStamp;
  84. speed = (int)(dataDiff * 1000 / timeDiff); // 1000是由ms转为s
  85. timeStamp = now;
  86. bytesAtTimeStamp = offset;
  87. }
  88. fs.Write(nbytes, 0, nReadSize);
  89. fs.Flush();
  90. offset += nReadSize;
  91. //Thread.Sleep(20);
  92. nReadSize = ns.Read(nbytes, 0, bytebuff);
  93. }
  94. if (!running)
  95. return true;
  96. if (offset != totalRemoteFileSize && running)//文件长度不等于下载长度,下载出错
  97. {
  98. throw new Exception(errorInfo);
  99. }
  100. if (running)
  101. finished = true;
  102. }
  103. catch (Exception ex)
  104. {
  105. if (ex.Message.Equals(errorInfo))
  106. throw ex;
  107. ++currentRetryTimes;
  108. if (currentRetryTimes > MaxTryTime)
  109. {
  110. throw ex;
  111. }
  112. }
  113. finally
  114. {
  115. if (ns != null)
  116. ns.Close();
  117. if (fs != null)
  118. fs.Close();
  119. if (request != null)
  120. request.Abort();
  121. }
  122. return false;
  123. }
  124. private FileStream getFileStream()
  125. {
  126. if (fs != null)
  127. {
  128. fs.Close();
  129. }
  130. if (File.Exists(destPath))
  131. {
  132. fs = File.OpenWrite(destPath);
  133. // offset < 0 则按照文件的大小定为offset
  134. if (offset < 0)
  135. {
  136. offset = fs.Length;
  137. }
  138. fs.Seek(offset, SeekOrigin.Current);//移动文件流中的当前指针
  139. }
  140. else
  141. {
  142. string dirName = Path.GetDirectoryName(destPath);
  143. if (!Directory.Exists(dirName))//如果不存在保存文件夹路径,新建文件夹
  144. {
  145. Directory.CreateDirectory(dirName);
  146. }
  147. fs = new FileStream(destPath, FileMode.Create);
  148. offset = 0;
  149. }
  150. return fs;
  151. }
  152. /// <summary>
  153. /// 获取下载文件长度
  154. /// </summary>
  155. /// <param name="url"></param>
  156. /// <returns></returns>
  157. public static long GetFileContentLength(string url)
  158. {
  159. HttpWebRequest request = null;
  160. try
  161. {
  162. request = (HttpWebRequest)HttpWebRequest.Create(url);
  163. request.Timeout = TimeOutWait;
  164. request.ReadWriteTimeout = ReadWriteTimeOut;
  165. //向服务器请求,获得服务器回应数据流
  166. WebResponse respone = request.GetResponse();
  167. request.Abort();
  168. return respone.ContentLength;
  169. }
  170. catch (Exception e)
  171. {
  172. if (request != null)
  173. request.Abort();
  174. return 0;
  175. }
  176. }
  177. private bool isDownloadFinished(long offset)
  178. {
  179. if (finished)
  180. return true;
  181. if (totalRemoteFileSize == 0)
  182. totalRemoteFileSize = GetFileContentLength(url);
  183. if (totalRemoteFileSize != 0 && totalRemoteFileSize == offset)
  184. {
  185. //下载完成
  186. finished = true;
  187. return true;
  188. }
  189. return false;
  190. }
  191. /// <summary>
  192. /// 获取当前速度
  193. /// </summary>
  194. /// <param name="formatted"></param>
  195. /// <returns></returns>
  196. public string getSpeed(bool formatted)
  197. {
  198. if (formatted)
  199. {
  200. if (finished)
  201. return "0 kB/s";
  202. else
  203. {
  204. return formatSpeed(speed, 0L, 0);
  205. }
  206. }
  207. else
  208. {
  209. if (finished)
  210. return "0";
  211. else
  212. return speed + "";
  213. }
  214. }
  215. private string formatSpeed(long speed, long decimalDigit, int depth)
  216. {
  217. if (speed < 1024)
  218. {
  219. return handleDigit(speed, decimalDigit) + getUnitByDepth(depth);
  220. }
  221. else
  222. {
  223. return formatSpeed(speed / 1024, speed % 1024, depth + 1);
  224. }
  225. }
  226. private string handleDigit(long integer, long decimalDigit)
  227. {
  228. if (decimalDigit == 0)
  229. {
  230. return integer.ToString();
  231. }
  232. else
  233. {
  234. return integer.ToString() + Math.Round((double)decimalDigit / 1024, 1).ToString().Substring(1);
  235. }
  236. }
  237. private string getUnitByDepth(int depth)
  238. {
  239. string val = string.Empty;
  240. switch (depth)
  241. {
  242. case 0:
  243. val = " B/s";
  244. break;
  245. case 1:
  246. val = " kB/s";
  247. break;
  248. case 2:
  249. val = " MB/s";
  250. break;
  251. case 3:
  252. val = " GB/s";
  253. break;
  254. default:
  255. val = " unknown/s";
  256. break;
  257. }
  258. return val;
  259. }
  260. public double getPercentage(int decimals)
  261. {
  262. if (totalRemoteFileSize == 0L)
  263. {
  264. return Math.Round(0d, decimals);
  265. }
  266. if (finished)
  267. return Math.Round(100d, decimals);
  268. double percentage = (double)offset * 100d / totalRemoteFileSize;
  269. return Math.Round(percentage, decimals);
  270. }
  271. }
  272. }