1. 程式人生 > >C#實現http多執行緒斷點續傳下載檔案

C#實現http多執行緒斷點續傳下載檔案

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);
        }
    }
}