最近項目需要做一個類似於迅雷的文件下載功能,這類需求可能比較常見,希望可以幫助到有需要的同學
要求:
1.支持斷點傳輸
2. 多文件同時下載
3. 由於是客戶內部試用,服務器只支持HTTP文件下載不支持FTP文件(並沒有用戶權限的要求)
由於根據服務器傳來的Json字符串解析後進行下載,首先構建Json對象,推薦一個庫Newtonsoft.Json
public class DownloadFile { [JsonProperty("police_num")] public string PoliceNumber { get; set; } [JsonProperty("equipment_num")] public string EquipmentNumber { get; set; } [JsonProperty("upload_date")] public DateTime UploadDateTime { get; set; } [JsonProperty("type")] public string FileType { get; set; } [JsonProperty("file_name")] public string FileName { get; set; } [JsonProperty("file_path")] public string FilePath { get; set; } }
JsonProperty不區分大小寫
public class DownloadTask : ObservableObject { /// <summary> /// </summary> public static readonly int BufferSize = 1024; /// <summary> /// 一次請求大小,每次請求100kb /// </summary> public static long Step = 1024*100; private int _process; /// <summary> /// 下載文件參數 /// </summary> public DownloadFile DownloadFile { get; set; } /// <summary> /// 下載文件狀態 /// </summary> public SegmentState SegmentState { get; set; } /// <summary> /// 默認存儲路徑 /// </summary> public string DefaultDirectory { get; set; } /// <summary> /// 當前進度 /// </summary> public long CurrentSize { get; set; } /// <summary> /// 文件流對象,用於生成文件 /// </summary> public FileStream FileStream { get; set; } /// <summary> /// 下載起始位置 /// </summary> public long RangeFrom { get; set; } /// <summary> /// 總大小 /// </summary> public long TotalSize { get; set; } /// <summary> /// 鏈接地址 /// </summary> public string Url { get; set; } /// <summary> /// 界面顯示進度 /// </summary> public int Process { get { return _process; } set { _process = value; RaisePropertyChanged("Process"); } } }
下載服務類,DownloaderService.cs,功能包括啟動下載,暫停,針對文件是否存在進行初始化,獲取文件大小方便界面顯示
public class DownloaderService { public void OnStart(DownloadTask downloadTask) { if (!InitService(downloadTask)) return; var downloadThread = new Thread(BeginDownloadFile) { IsBackground = true }; downloadThread.Start(downloadTask); } public void OnStop(DownloadTask downloadTask) { downloadTask.SegmentState = SegmentState.Paused; } private bool InitService(DownloadTask downloadTask) { try { if (string.IsNullOrEmpty(downloadTask.Url)) { return false; } downloadTask.DownloadFile.FileName = downloadTask.Url.Substring(downloadTask.Url.LastIndexOf('/') + 1); var fullName = Path.Combine(downloadTask.DefaultDirectory, downloadTask.DownloadFile.FileName); if (File.Exists(fullName)) { if (downloadTask.TotalSize == 0) { GetTotalSize(downloadTask); } if (downloadTask.CurrentSize == downloadTask.TotalSize) { if (MessageBox.Show("文件已經存在是否覆蓋此文件", "TCL", MessageBoxButton.YesNo, MessageBoxImage.Information) != MessageBoxResult.Yes) return false; File.Delete(fullName); downloadTask.CurrentSize = 0; } downloadTask.FileStream = new FileStream(fullName, FileMode.OpenOrCreate, FileAccess.ReadWrite); downloadTask.SegmentState = SegmentState.Downloading; return true; } downloadTask.SegmentState = SegmentState.Downloading; downloadTask.FileStream = new FileStream(fullName, FileMode.OpenOrCreate, FileAccess.ReadWrite); return true; } catch (Exception ex) { LogLightConsole.WriteLog(ex.ToString(), EunmMsgType.Error); return false; } } private void BeginDownloadFile(object obj) { try { var downloadTask = (DownloadTask)obj; while (true) { if (downloadTask.SegmentState == SegmentState.Paused) { downloadTask.FileStream.Close(); break; } //從0計數,需要減一 var from = downloadTask.CurrentSize; if (from < 0) { from = 0; } var to = downloadTask.CurrentSize + DownloadTask.Step - 1; if (to >= downloadTask.TotalSize && downloadTask.TotalSize > 0) { to = downloadTask.TotalSize - 1; } if (downloadTask.TotalSize == 0) { GetTotalSize(downloadTask); } if (from >= downloadTask.TotalSize || downloadTask.CurrentSize >= downloadTask.TotalSize) { downloadTask.SegmentState = SegmentState.Finished; downloadTask.FileStream.Close(); downloadTask.Process = 100; return; } var request = (HttpWebRequest)WebRequest.Create(downloadTask.Url); //request.Method = "GET"; request.AddRange("bytes", from, to); request.Timeout = 1000 * 20; var response = (HttpWebResponse)request.GetResponse(); var buffer = new byte[1024]; using (var stream = response.GetResponseStream()) { if (stream != null) { var size = stream.Read(buffer, 0, buffer.Length); while (size > 0) { //只將讀出的字節寫入文件 downloadTask.FileStream.Write(buffer, 0, size); downloadTask.CurrentSize += size; size = stream.Read(buffer, 0, buffer.Length); downloadTask.Process = (int)(downloadTask.CurrentSize * 100 / downloadTask.TotalSize); downloadTask.FileStream.Flush(); } } //如果返回的response頭中Content-Range值為空,說明服務器不支持Range屬性,不支持斷點續傳,返回的是所有數據 if (response.Headers["Content-Range"] == null) { downloadTask.SegmentState = SegmentState.Finished; } } } } catch (Exception ex) { LogLightConsole.WriteLog("BeginDownloadFile" + ex, EunmMsgType.Error); } } /// <summary> /// 獲取文件總大小 /// </summary> public void GetTotalSize(DownloadTask downloadTask) { var request = (HttpWebRequest)WebRequest.Create(downloadTask.Url); //request.Method = "POST"; //request.Headers.Add("charset:" + "utf-8"); request.Timeout = 1000 * 20; var response = (HttpWebResponse)request.GetResponse(); downloadTask.TotalSize = response.ContentLength; } }
HTTP 1.1版本本身已經擴展了斷點傳輸功能,但是這需要服務器同時也支持,這只是提供了一個簡單的例子,具體的針對數據上下文以及成功失敗的callback函數,就是做一下委托封裝用就好,如果具體使用有什麼問題可以私信我,謝謝大家支持!
response.Headers["Content-Range"] == null 說明服務器不支持斷點傳輸