HttpDownloader.cs 9.6 KB

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