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