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();
} }
FileStream实现多线程断点续传(已封装)的更多相关文章
- C#基础-FileStream实现多线程断点续传
一.前言 网上有许多的多线程断点续传操作,但总是写的很云里雾里,或者写的比较坑长.由于这几个月要负责公司的在线升级项目,所以正好顺便写了一下 代码如下: using System; using Sys ...
- AsyncTask实现多任务多线程断点续传下载
这篇博客是AsyncTask下载系列的最后一篇文章,前面写了关于断点续传的和多线程下载的博客,这篇是在前两篇的基础上面实现的,有兴趣的可以去看下. 一.AsyncTask实现断点续传 二.AsyncT ...
- 实现android支持多线程断点续传下载器功能
多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...
- android 多线程断点续传下载
今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...
- Android开发多线程断点续传下载器
使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点 ...
- 多线程更新已排序的Datagridview数据,造成数据错位
多线程更新已排序的Datagridview数据,触发Datagridview的auto-sort时间,数据重新排序,造成后面更新数据的更新错误. 解决方法: 方法一.设置Datagridview的表头 ...
- Android多线程断点续传下载
这个月接到一个项目.要写一个像360助手一样的对于软件管理的APP:当中.遇到了一个问题:多线程断点下载 这个 ,因为之前没有写过这方面的应用功能.所以.不免要自学了. 然后就在各个昂站上收索并整理了 ...
- Android 多线程断点续传同时下载多个大文件
最近学习在Android环境中一些网络请求方面的知识,其中有一部分是关于网络下载方面的知识.在这里解析一下自己写的demo,总结一下自己所学的知识.下图为demo的效果图,仿照一些应用下载商城在Lis ...
- android多线程断点续传下载文件
一.目标 1.多线程抢占服务器资源下载. 2.断点续传. 二.实现思路. 假设分为三个线程: 1.各个线程分别向服务器请求文件的不同部分. 这个涉及Http协议,可以在Header中使用Range参数 ...
随机推荐
- Cannot call sendError() after the response has been committed - baiyangliu
当response提交后,不能调用sendError(),什么意思? 出现这个错误,一定是多次response导致的.可以这么理解,承载客户端和服务器进行Http交互的Socket连接已经关闭了,而你 ...
- GCD (Grand Central Dispatch) 笔记
GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式. 在Mac OS X 10.6和IOS 4.0之后开 ...
- APP图标制作以及替换步骤
1 首先要有一张1024X1024像素以上的的大图片(长宽最好相等) 2 如果app图标需要的是圆角的,那先通过以下这个工具转换一下: http://www.360doc.com/content/ ...
- Dubbo -- 系统学习 笔记 -- 示例 -- 负载均衡
Dubbo -- 系统学习 笔记 -- 目录 示例 想完整的运行起来,请参见:快速启动,这里只列出各种场景的配置方式 负载均衡 在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调 ...
- Springboot启动后报错【This application has no explicit mapping for /error, so you are seeing this as a fallback····】
This application has no explicit mapping for /error, so you are seeing this as a fallback. Wed Dec 1 ...
- 利用Python爆破数据库备份文件
某次测试过程中,发现PHP备份功能代码如下: // 根据时间生成备份文件名 $file_name = 'D' . date('Ymd') . 'T' . date('His'); $sql_file_ ...
- 使用 requests 发送 POST 请求
POST请求也就是向服务器提交数据,通常我们用来提交表单数据: import requests postdata = { //定义表单数据 "username": "ab ...
- List 集合的N层遍历
package com.j1.cms.model; import java.io.Serializable; import java.util.List; /** * Created by wangc ...
- LinQ的初步学习与总结
嘿嘿,说起来ORM和LinQ,就感觉离我好遥远的,在学校是没有学习的,所以总感觉学习了LinQ就是大神,现在嘛,终于也体会一点,感觉LinQ只是初步学习,没有太难,当然以后使用在项目中就没有这样的简单 ...
- 《C++标准程序库》笔记之三
本篇博客笔记顺序大体按照<C++标准程序库(第1版)>各章节顺序编排. ---------------------------------------------------------- ...