C#實現http多執行緒斷點續傳下載檔案
阿新 • • 發佈:2019-01-24
using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Net; using u8 = System.Byte; using u16 = System.UInt16; using s32 = System.Int32; using u32 = System.UInt32; using f32 = System.Single; namespace ConsoleApplication1 { public class HttpDownloadFile { public void SetURL(string URL) { mURL = URL; } //單執行緒下載檔案大小閥值 public void SetSingleThFileSize(u32 FileSize) { mSingleThFileSize = FileSize; } //下載速度KB/S,需要外部迴圈呼叫Run函式更新這個值 public f32 GetDownloadSpeed() { return mDownloadSpeed / 1024; } //新增非同步下載檔案,這裡提供輸入檔案大小的原因是 //方便靈活擴充套件,比如遊戲更新,我們有可能先同步下載一個version.txt的 //檔案裡面包含類似md5和檔案大小與本地比較找到需要下載的檔案 //這時我們已經可以從version.txt裡面提前知道需要下載檔案的大小了 public void AddAsyncDownloadFile(string RemoteFilePath, string LocalFilePath, u32 RemoteFileSize = 0) { mDownloadFiles.Add(new DownloadFile(RemoteFilePath, LocalFilePath, RemoteFileSize)); } //同步下載檔案 public bool SyncDownloadFile(string RemoteFilePath, string LocalFilePath) { try { var HttpRequest = WebRequest.Create(mURL + "//" + RemoteFilePath) as HttpWebRequest; var HttpResponse = HttpRequest.GetResponse() as HttpWebResponse; var HttpStream = HttpResponse.GetResponseStream(); _PrepareDirForFile(LocalFilePath); var OutStream = new FileStream(LocalFilePath, FileMode.Create); var Buffer = new Byte[1024]; var ReadBytes = 0; while (true) { ReadBytes = HttpStream.Read(Buffer, 0, Buffer.Length); if (ReadBytes <= 0) { break; } OutStream.Write(Buffer, 0, ReadBytes); } OutStream.Close(); HttpStream.Close(); HttpResponse.Close(); HttpRequest.Abort(); return true; } catch (WebException e) { Console.WriteLine(e.Message); return false; } catch (Exception e) { Console.WriteLine(e.Message); return false; } } public void StartAsyncDownloadFiles() { _CutFile(); Thread t; for (s32 i = 0; i < Environment.ProcessorCount; ++i) { t = new Thread(new ParameterizedThreadStart(_AsyncDownloadFiles)); t.Start(i); } //檔案下載進度、總下載進度、下載完成事件請自行添加了... } void Run(f32 TimeDelta) { if (mDownloadBytes >= mTotalDownloadBytes) { return; } mTimeDelta += TimeDelta; if (mTimeDelta >= 1.0f) { mDownloadSpeed = mDownloadBytesCounter; mTimeDelta = .0f; mDownloadBytesCounter = 0; } } //獲取檔案大小,並且分割檔案或者臨時檔案 void _CutFile() { HttpWebRequest HttpRequest; HttpWebResponse HttpResponse; DownloadFile MyFile; u32 Block = 0; u32 Mod = 0; FileStream InStream; string LocalTempFilePath = String.Empty; for (s32 i = 0; i < mDownloadFiles.Count; ++i) { MyFile = mDownloadFiles[i]; //得到檔案大小 if (MyFile.mRemoteFileSize <= 0) { try { HttpRequest = WebRequest.Create(mURL + "//" + MyFile.mRemoteFilePath) as HttpWebRequest; HttpResponse = HttpRequest.GetResponse() as HttpWebResponse; MyFile.mRemoteFileSize = (u32)HttpResponse.ContentLength; HttpResponse.Close(); HttpRequest.Abort(); } catch (WebException e) { Console.WriteLine(e.Message); continue; } } //檢測是否有未下載完畢的臨時檔案 for (s32 j = 0; j < Environment.ProcessorCount; ++j) { if (File.Exists(MyFile.mLocalFilePath + j.ToString())) { LocalTempFilePath = MyFile.mLocalFilePath + j.ToString(); break; } } //檔案塊大小 Block = MyFile.mRemoteFileSize / (u32)Environment.ProcessorCount; //餘數 Mod = MyFile.mRemoteFileSize % (u32)Environment.ProcessorCount; if (LocalTempFilePath.Length > 0) { if (MyFile.mRemoteFileSize < mSingleThFileSize) { InStream = new FileStream(LocalTempFilePath, FileMode.Open); MyFile.mDownloadBytes += (u32)InStream.Length; MyFile.mFileBlocks.Enqueue(new DownloadFile.FileBlock((u32)InStream.Length, MyFile.mRemoteFileSize - 1, LocalTempFilePath)); InStream.Close(); } else { DownloadFile.FileBlock MyFileBlock; for (u32 j = 0; j < Environment.ProcessorCount; ++j) { LocalTempFilePath = MyFile.mLocalFilePath + j.ToString(); if (File.Exists(LocalTempFilePath)) { InStream = new FileStream(LocalTempFilePath, FileMode.Open); MyFile.mDownloadBytes += (u32)InStream.Length; MyFileBlock = new DownloadFile.FileBlock(j * Block + (u32)InStream.Length, (j + 1) * Block - 1, LocalTempFilePath); if (j == Environment.ProcessorCount - 1) { MyFileBlock.mEndBlock += Mod; } MyFile.mFileBlocks.Enqueue(MyFileBlock); InStream.Close(); } else { MyFileBlock = new DownloadFile.FileBlock(j * Block, (j + 1) * Block - 1, LocalTempFilePath); if (j == Environment.ProcessorCount - 1) { MyFileBlock.mEndBlock += Mod; } MyFile.mFileBlocks.Enqueue(MyFileBlock); } } } LocalTempFilePath = String.Empty; } else { if (MyFile.mRemoteFileSize < mSingleThFileSize) { MyFile.mFileBlocks.Enqueue(new DownloadFile.FileBlock(0, MyFile.mRemoteFileSize - 1)); } else { DownloadFile.FileBlock MyBlock; for (u32 j = 0; j < Environment.ProcessorCount; ++j) { MyBlock = new DownloadFile.FileBlock(j * Block, (j + 1) * Block - 1); if (j == Environment.ProcessorCount - 1) { MyBlock.mEndBlock += Mod; } MyFile.mFileBlocks.Enqueue(MyBlock); } } } mDownloadBytes += MyFile.mDownloadBytes; mTotalDownloadBytes += MyFile.mRemoteFileSize - MyFile.mDownloadBytes; } } private void _AsyncDownloadFiles(System.Object o) { var ThreadID = (u16)o; HttpWebRequest HttpRequest; HttpWebResponse HttpResponse; Stream HttpStream; FileStream OutStream; s32 ReadBytes = 0; Byte[] Buffer = new Byte[1024]; DownloadFile FileQueue; DownloadFile.FileBlock BlockQueue; while (true) { lock (this) { if (mDownloadFiles.Count <= 0) { break; } FileQueue = mDownloadFiles[0]; BlockQueue = FileQueue.mFileBlocks.Dequeue(); } try { //臨時檔案有可能恰好下完,這裡就不用在請求了 if (BlockQueue.mBeginBlock > BlockQueue.mEndBlock) { goto SKIP_REQUEST; } HttpRequest = WebRequest.Create(mURL + "//" + FileQueue.mRemoteFilePath) as HttpWebRequest; HttpRequest.AddRange(BlockQueue.mBeginBlock, BlockQueue.mEndBlock); HttpResponse = HttpRequest.GetResponse() as HttpWebResponse; HttpStream = HttpResponse.GetResponseStream(); //下載全新的臨時檔案 if ("" == BlockQueue.mTempFilePath) { _PrepareDirForFile(FileQueue.mLocalFilePath + ThreadID.ToString()); OutStream = new FileStream(FileQueue.mLocalFilePath + ThreadID.ToString(), FileMode.Create); } //繼續下載未下載完的臨時檔案 else { OutStream = new FileStream(BlockQueue.mTempFilePath, FileMode.Append); } while (true) { ReadBytes = HttpStream.Read(Buffer, 0, Buffer.Length); if (ReadBytes <= 0) { break; } OutStream.Write(Buffer, 0, ReadBytes); lock (this) { FileQueue.mDownloadBytes += (u32)ReadBytes; mDownloadBytesCounter = mDownloadBytes += (u32)ReadBytes; f32 FilePercent = (f32)FileQueue.mDownloadBytes / FileQueue.mRemoteFileSize;//事件呼叫檔案進度 //onDownloadFilePercent(FilePercent * 100, FileQueue.mRemoteFilePath); f32 TotalPercent = (f32)mDownloadBytes / mTotalDownloadBytes;//事件呼叫總進度 //onDownloadPercent(TotalPercent * 100); } } OutStream.Close(); HttpStream.Close(); HttpResponse.Close(); HttpRequest.Abort(); SKIP_REQUEST: lock (this) { //一個檔案所有塊下載完畢,合併檔案 if (FileQueue.mFileBlocks.Count <= 0) { _MergeFile(FileQueue, BlockQueue, ThreadID); mDownloadFiles.RemoveAt(0); } } } catch (WebException e) { Console.WriteLine(e.Message); } catch (Exception e) { Console.WriteLine(e.Message); } } } private void _MergeFile(DownloadFile MyFile, DownloadFile.FileBlock Block, u16 ThreadID) { if (MyFile.mRemoteFileSize < mSingleThFileSize) { if (File.Exists(MyFile.mLocalFilePath)) { File.Delete(MyFile.mLocalFilePath); } if ("" == Block.mTempFilePath) { File.Move(MyFile.mLocalFilePath + ThreadID.ToString(), MyFile.mLocalFilePath); } else { File.Move(Block.mTempFilePath, MyFile.mLocalFilePath); } return; } FileStream OutStream = new FileStream(MyFile.mLocalFilePath, FileMode.Create); FileStream InStream; s32 ReadBytes = 0; Byte[] Buffer = new Byte[1024]; for (u32 i = 0; i < Environment.ProcessorCount; ++i) { InStream = new FileStream(MyFile.mLocalFilePath + i.ToString(), FileMode.Open); while (true) { ReadBytes = InStream.Read(Buffer, 0, Buffer.Length); if (ReadBytes <= 0) { break; } OutStream.Write(Buffer, 0, ReadBytes); } InStream.Close(); File.Delete(MyFile.mLocalFilePath + i.ToString()); } OutStream.Close(); } private void _PrepareDirForFile(string FilePath) { var Dir = Path.GetDirectoryName(FilePath); if (false == Directory.Exists(Dir)) { Directory.CreateDirectory(Dir); } } class DownloadFile { public DownloadFile(string RemoteFilePath, string LocalFilePath, u32 RemoteFileSize = 0) { mRemoteFilePath = RemoteFilePath; mLocalFilePath = LocalFilePath; mRemoteFileSize = RemoteFileSize; mDownloadBytes = 0; } public string mRemoteFilePath; public string mLocalFilePath; public u32 mRemoteFileSize; public u32 mDownloadBytes; public class FileBlock { public FileBlock(u32 BeginBlock, u32 EndBlock, string TempFilePath = "") { mBeginBlock = BeginBlock; mEndBlock = EndBlock; mTempFilePath = TempFilePath; } public u32 mBeginBlock; public u32 mEndBlock; public string mTempFilePath; } public Queue<FileBlock> mFileBlocks = new Queue<FileBlock>(); } List<DownloadFile> mDownloadFiles = new List<DownloadFile>(); string mURL = "http://127.0.0.1:8080//download"; u32 mSingleThFileSize =1024 * 1024 * 10; f32 mDownloadSpeed = .0f; f32 mTimeDelta = .0f; u32 mDownloadBytesCounter = 0; u32 mDownloadBytes = 0; u32 mTotalDownloadBytes = 0; } class Program { static void Main(string[] args) { //程式碼寫的不太完整,但是核心部分和流程已經可以作為參考(臨時寫的只是編譯通過,未做測試) HttpDownloadFile Download = new HttpDownloadFile(); Download.SetURL( "http://127.0.0.1:8080//download" ); Download.SetSingleThFileSize( 1024 * 1024 * 10 ); Download.AddAsyncDownloadFile("1.rmvb", "download//1.rmvb"); Download.AddAsyncDownloadFile("2.rmvb", "download//2.rmvb"); Download.StartAsyncDownloadFiles(); Thread.Sleep(100000); } } }