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

时间:2023-03-10 02:50:00
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; } = * * ; /// <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, * * )
{ } /// <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) > )
{
this.PackCount = (int)(this.FileLength / this.PackSize) + ;
} 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 = ; 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, , 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 = ; j < PackCount; j++)
{
bool hasfile = false;
foreach (FileInfo Tempfile in tempfiles)
{
if (Tempfile.Name.Split('_')[] == j.ToString())
{
hasfile = true;
break;
}
}
if (hasfile == false)
{
Comparefiles.Add(j.ToString());
}
} //最后补上这些缺失的文件
if (Comparefiles.Count > )
{
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, , 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, , Convert.ToInt32(onefileLength));
writestream.Write(buffer, , 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, * , true))
{
byte[] buffer = new byte[length];
ServerStream.Position = start;
//ServerStream.Seek(start, SeekOrigin.Begin);
ServerStream.Read(buffer, , 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, '') + "_" + 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 = * ;
byte[] buffer = new byte[bufferSize];
Stream inputStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
HashAlgorithm hashAlgorithm = new MD5CryptoServiceProvider();
int readLength = ;//每次读取长度
var output = new byte[bufferSize];
while ((readLength = inputStream.Read(buffer, , buffer.Length)) > )
{
//计算MD5
hashAlgorithm.TransformBlock(buffer, , readLength, output, );
}
//完成最后计算,必须调用(由于上一部循环已经完成所有运算,所以调用此方法时后面的两个参数都为0)
hashAlgorithm.TransformFinalBlock(buffer, , );
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();
for (int i = ; i < result.Length; i++)
{
strbul.Append(result[i].ToString("x2"));//加密结果"x2"结果为32位,"x3"结果为48位,"x4"结果为64位 }
return strbul.ToString();
} }