FileStream实现多线程断点续传(已封装)

 

  • 处理文件分片
  • 处理缺失的分片文件
  • 合并分片文件
  • MD5验证文件

 

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Linq;
using System.Text;

public class FileTransfer
{

    /// <summary>
    /// 文件源路径
    /// </summary>
    public string SrcPath { get; private set; }

    /// <summary>
    /// 文件的目标路径
    /// </summary>
    public string TgtPath { get; private set; }

    /// <summary>
    /// 临时目录(存放断点数据文件)
    /// </summary>
    public string TempDir { get; private set; }

    /// <summary>
    /// 文件的目标目录
    /// </summary>
    public string TgtDir { get; private set; }


    /// <summary>
    /// 数据包大小(默认16mb)
    /// </summary>
    public long PackSize { get; set; } = 1024 * 1024 * 16;


    /// <summary>
    /// 文件大小
    /// </summary>
    public long FileLength { get; private set; }

    /// <summary>
    /// 传输包大小
    /// </summary>
    public int PackCount { get; private set; }


    /// <summary>
    /// 断点续传
    /// </summary>
    /// <param name="srcPath">文件源路径</param>
    /// <param name="tgtPath">文件的目标路径</param>
    public FileTransfer(string srcPath, string tgtPath)
        : this(srcPath, tgtPath, 1024 * 1024 * 16)
    {

    }

    /// <summary>
    /// 断点续传
    /// </summary>
    /// <param name="srcPath">文件源路径</param>
    /// <param name="tgtPath">文件的目标路径</param>
    /// <param name="packSize">数据包大小</param>
    public FileTransfer(string srcPath, string tgtPath, int packSize)
    {
        this.SrcPath = srcPath;
        this.TgtPath = tgtPath;
        this.PackSize = packSize;

        FileInfo fileInfo = new FileInfo(this.SrcPath);
        if (!fileInfo.Exists)
        {
            throw new ArgumentException("文件不存在!", "srcPath");
        }

        this.TgtDir = Path.GetDirectoryName(tgtPath);

        if (!Directory.Exists(this.TgtDir))
        {
            Directory.CreateDirectory(this.TgtDir);
        }

        this.FileLength = fileInfo.Length;

        if ((this.FileLength % this.PackSize) > 0)
        {
            this.PackCount = (int)(this.FileLength / this.PackSize) + 1;
        }

        else
        {
            this.PackCount = (int)(this.FileLength / this.PackSize);
        }

        this.TempDir = Path.Combine(this.TgtDir, StrMD5(Path.GetFileName(this.TgtPath)));

        //新new 对象时,删除临时文件夹
        if (Directory.Exists(this.TempDir))
        {
            Directory.Delete(this.TempDir, true);
        }
    }

    /// <summary>
    /// 检测临时目录是否存在,不存在则创建
    /// </summary>
    private void CheckTempDir()
    {
        if (!Directory.Exists(this.TempDir))
        {
            Directory.CreateDirectory(this.TempDir);
        }
    }

    /// <summary>
    /// md5比对文件
    /// </summary>
    /// <returns></returns>
    public bool Md5Compare()
    {
        string md51 = FileMD5(this.SrcPath);
        string md52 = FileMD5(this.TgtPath);

        if (md51 == null || md52 == null)
        {
            return false;
        }
        return md51.Equals(md52);
    }


    /// <summary>
    /// 文件分片传输
    /// </summary>
    public void Transfer(bool isMerge = true)
    {
        CheckTempDir();
        //多线程任务
        var tasks = new Task[this.PackCount];
        var fy = Task.Factory;
        for (int index = 0; index < this.PackCount; index++)
        {
            long Threadindex = index; //这步很关键,在Task()里的绝对不能直接使用index
            var task = fy.StartNew(() =>
            {
                //临时文件路径
                string tempfilepath = Path.Combine(this.TempDir, GenerateTempName(Threadindex));
                using (FileStream tempstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
                {
                    int length = (int)Math.Min(PackSize, FileLength - Threadindex * PackSize);

                    var bytes = GetFile(Threadindex * PackSize, length);

                    tempstream.Write(bytes, 0, length);
                    tempstream.Flush();
                    tempstream.Close();
                    tempstream.Dispose();
                }
            });
            tasks[Threadindex] = task;
        }
        //等待所有线程完成
        Task.WaitAll(tasks);

        // 合并文件
        if (isMerge)
        {
            Merge();
        }
    }


    /// <summary>
    /// 比对缓存文件,并进行分片
    /// </summary>
    public void CompareTransfer(bool isMerge = true)
    {
        CheckTempDir();
        //临时文件夹路径
        var tempfiles = new DirectoryInfo(this.TempDir).GetFiles();
        List<string> Comparefiles = new List<string>();
        for (int j = 0; j < PackCount; j++)
        {
            bool hasfile = false;
            foreach (FileInfo Tempfile in tempfiles)
            {
                if (Tempfile.Name.Split('_')[1] == j.ToString())
                {
                    hasfile = true;
                    break;
                }
            }
            if (hasfile == false)
            {
                Comparefiles.Add(j.ToString());
            }
        }

        //最后补上这些缺失的文件
        if (Comparefiles.Count > 0)
        {
            var tasks = new List<Task>();
            var fy = Task.Factory;
            foreach (string com_index in Comparefiles)
            {
                string strIndex = com_index;
                var task = fy.StartNew(() =>
                {
                    string tempfilepath = Path.Combine(this.TempDir, GenerateTempName(strIndex));
                    using (FileStream Compstream = new FileStream(tempfilepath, FileMode.Create, FileAccess.Write, FileShare.Write))
                    {
                        int length = (int)Math.Min(PackSize, this.FileLength - Convert.ToInt32(strIndex) * this.PackSize);
                        var bytes = GetFile(Convert.ToInt64(strIndex) * PackSize, length);
                        Compstream.Write(bytes, 0, length);
                        Compstream.Flush();
                        Compstream.Close();
                        Compstream.Dispose();
                    }
                });
                tasks.Add(task);
            }
            //等待所有线程完成
            Task.WaitAll(tasks.ToArray());
        }

        // 合并文件
        if (isMerge)
        {
            Merge();
        }
    }

    /// <summary>
    /// 合并分片文件
    /// </summary>
    /// <param name="isDelTemp">是否删除临时文件夹(文件)</param>
    public void Merge(bool isDelTemp = true)
    {
        //var tempDirInfo = new DirectoryInfo(this.TempDir);
        //using (FileStream writestream = new FileStream(this.TgtPath, FileMode.Create, FileAccess.Write, FileShare.Write))
        //{
        //    var tempfiles = tempDirInfo.GetFiles();
        //    foreach (FileInfo fileInfo in tempfiles)
        //    {
        //        Console.WriteLine(fileInfo.Name);
        //        using (FileStream readTempStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        //        {
        //            long onefileLength = fileInfo.Length;
        //            byte[] buffer = new byte[Convert.ToInt32(onefileLength)];
        //            readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength));
        //            writestream.Write(buffer, 0, Convert.ToInt32(onefileLength));
        //        }
        //    }
        //    writestream.Flush();
        //    writestream.Close();
        //    writestream.Dispose();
        //}
        //tempDirInfo.Delete(isDelTemp);

        var tempDirInfo = new DirectoryInfo(this.TempDir);
        using (FileStream writestream = new FileStream(this.TgtPath, FileMode.Create, FileAccess.Write, FileShare.Write))
        {
            var tempfiles = tempDirInfo.GetFiles();
            foreach (FileInfo fileInfo in tempfiles)
            {
                using (FileStream readTempStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                {
                    long onefileLength = fileInfo.Length;
                    byte[] buffer = new byte[Convert.ToInt32(onefileLength)];
                    readTempStream.Read(buffer, 0, Convert.ToInt32(onefileLength));
                    writestream.Write(buffer, 0, Convert.ToInt32(onefileLength));
                }

                if (isDelTemp)
                {
                    fileInfo.Delete();
                }
            }
            writestream.Flush();
            writestream.Close();
            writestream.Dispose();
        }

        if (isDelTemp)
        {
            tempDirInfo.Delete(isDelTemp);
        }
    }


    /// <summary>
    /// 根据开始位置获取文件字节流
    /// </summary>
    /// <param name="start"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    private byte[] GetFile(long start, int length)
    {
        using (FileStream ServerStream = new FileStream(this.SrcPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1024 * 80, true))
        {
            byte[] buffer = new byte[length];
            ServerStream.Position = start;
            //ServerStream.Seek(start, SeekOrigin.Begin);
            ServerStream.Read(buffer, 0, length);
            return buffer;
        }
    }

    /// <summary>
    /// 生成临时文件名称
    /// </summary>
    /// <param name="index"></param>
    /// <returns></returns>
    private string GenerateTempName(object index)
    {
        string res = index.ToString().PadLeft(this.PackCount.ToString().Length, '0') + "_" + this.PackCount;
        Console.WriteLine(res);
        return res;
    }


    /// <summary>
    /// 计算文件的Md5
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    private static string FileMD5(string path)
    {
        if (!File.Exists(path))
        {
            return null;
        }
        int bufferSize = 1024 * 16;
        byte[] buffer = new byte[bufferSize];
        Stream inputStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
        HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
        int readLength = 0;//每次读取长度
        var output = new byte[bufferSize];
        while ((readLength = inputStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            //计算MD5
            hashAlgorithm.TransformBlock(buffer, 0, readLength, output, 0);
        }
        //完成最后计算,必须调用(由于上一部循环已经完成所有运算,所以调用此方法时后面的两个参数都为0)
        hashAlgorithm.TransformFinalBlock(buffer, 0, 0);
        string md5 = BitConverter.ToString(hashAlgorithm.Hash).Replace("-", "");
        hashAlgorithm.Clear();
        inputStream.Close();
        inputStream.Dispose();
        return md5;
    }

    /// <summary>
    /// 字符串Md5
    /// </summary>
    /// <param name="source"></param>
    /// <returns></returns>
    private static string StrMD5(string source)
    {
        byte[] sor = Encoding.UTF8.GetBytes(source);
        MD5 md5 = MD5.Create();
        byte[] result = md5.ComputeHash(sor);
        StringBuilder strbul = new StringBuilder(40);
        for (int i = 0; i < result.Length; i++)
        {
            strbul.Append(result[i].ToString("x2"));//加密结果"x2"结果为32位,"x3"结果为48位,"x4"结果为64位

        }
        return strbul.ToString();
    }

}

 

posted @ 2018-11-26 10:11  TakeTry  阅读(679)  评论(0编辑  收藏  举报