一、开篇描述
本篇博客所描述的断点续传功能是基于c#语言,服务器端采用.net mvc框架,客户端采用winform框架。
本篇博客实现断点续传功能的基本思路:1)服务器端是把接收到的文件流,追加到已有的文件;2)客户端是把文件流截段上传;
其实,任何一种计算机语言基于这个思路(web客户端JavaScript目前例外),都可以实现断点续传的功能。
二、服务器端
namespace MvcApplicationUpload.Controllers
{
public class HomeController : Controller
{
/// <summary>
/// Index
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
//string ip = Request.UserHostAddress;
//string port = Request.ServerVariables["REMOTE_PORT"].ToString();
return View();
}
/// <summary>
/// Test
/// </summary>
/// <returns></returns>
public ActionResult Test()
{
return PartialView();
}
/// <summary>
/// 获取续传点。
/// </summary>
/// <param name="md5Name"></param>
/// <returns></returns>
public string GetFileResumePoint(string md5Name)
{
var saveFilePath = Server.MapPath("~/Images/") + md5Name;
if (System.IO.File.Exists(saveFilePath))
{
var fs = System.IO.File.OpenWrite(saveFilePath);
var fsLength = fs.Length.ToString();
fs.Close();
return fsLength;
}
return "0";
}
/// <summary>
/// 文件续传。
/// </summary>
/// <returns></returns>
[HttpPost]
public HttpResponseMessage FileResume()
{
var md5Name = Request.QueryString["md5Name"];
var fileStream = Request.InputStream;
var contentRange = Request.Headers["Content-Range"];//contentRange样例:bytes 0-1000/5000
SaveAs(Server.MapPath("~/Images/") + md5Name, fileStream, contentRange);
return new HttpResponseMessage(HttpStatusCode.OK);
}
/// <summary>
/// 给已有文件追加文件流。
/// </summary>
/// <param name="saveFilePath"></param>
/// <param name="stream"></param>
private void SaveAs(string saveFilePath, System.IO.Stream stream, string contentRange)
{
//接收到的碎片字节流信息。
long startPosition = 0;
long endPosition = 0;
if (!string.IsNullOrEmpty(contentRange))
{
contentRange = contentRange.Replace("bytes", "").Trim();
contentRange = contentRange.Substring(0, contentRange.IndexOf("/"));
string[] ranges = contentRange.Split('-');
startPosition = long.Parse(ranges[0]);
endPosition = long.Parse(ranges[1]);
}
else
{
return;
}
//默认写针位置。
System.IO.FileStream fs;
long writeStartPosition = 0;
if (System.IO.File.Exists(saveFilePath))
{
fs = System.IO.File.OpenWrite(saveFilePath);
writeStartPosition = fs.Length;
}
else
{
fs = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create);
}
//调整写针位置。
if (writeStartPosition >= endPosition)
{
fs.Close();
return;
}
else if (writeStartPosition < startPosition)
{
fs.Close();
return;
}
else if (writeStartPosition > startPosition && writeStartPosition < endPosition)
{
writeStartPosition = startPosition;
}
fs.Seek(writeStartPosition, System.IO.SeekOrigin.Current);
//向文件追加文件流。
byte[] nbytes = new byte[512];
int nReadSize = 0;
while ((nReadSize = stream.Read(nbytes, 0, 512)) > 0)
{
fs.Write(nbytes, 0, nReadSize);
}
fs.Close();
}
}
}
三、客户端
1 界面如下:
2 代码如下:
namespace WindowsFormsApplicationUpload
{
public partial class Form1 : Form
{
/// <summary>
/// 定义委托。
/// </summary>
/// <param name="text"></param>
delegate void ChangeText(string text);
/// <summary>
/// 暂停标记。
/// </summary>
int flag = 0;
/// <summary>
/// 上传的文件路径。
/// </summary>
string filePath;
/// <summary>
/// 上传的文件md5值。
/// </summary>
string md5Str;
/// <summary>
/// 上传的文件续传点。
/// </summary>
long startPoint;
public Form1()
{
InitializeComponent();
}
/// <summary>
/// 委托方法。
/// </summary>
/// <param name="text"></param>
public void ChangeLabelText(string text)
{
this.label1.Text = text;
}
/// <summary>
/// 得到文件的MD5值。
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public string GetFileMd5(string filePath)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
Byte[] hashBytes = md5.ComputeHash(fs);
fs.Close();
return BitConverter.ToString(hashBytes).Replace("-", "");
}
/// <summary>
/// 得到文件的续传点。
/// </summary>
/// <param name="md5Str"></param>
/// <param name="fileExtension"></param>
/// <returns></returns>
public long GetFileResumePoint(string md5Str, string fileExtension)
{
System.Net.WebClient webClient = new System.Net.WebClient();
string urlStr = "http://127.0.0.1/Home/GetFileResumePoint?md5Name=" + md5Str + fileExtension;
byte[] infoBytes = webClient.DownloadData(urlStr);
string result = System.Text.Encoding.UTF8.GetString(infoBytes);
webClient.Dispose();
if (string.IsNullOrEmpty(result))
{
return 0;
}
return Convert.ToInt64(result);
}
public void ResumeFileThread()
{
MessageBox.Show(ResumeFile("http://127.0.0.1/Home/FileResume", filePath, startPoint, 1024 * 128, md5Str));
}
public string ResumeFile(string hostUrl, string filePath, long startPoint, int byteCount, string md5Str)
{
string result = "上传成功!";
if (startPoint < 0)
{
result = "指针有误!";
return result;
}
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
long fileLength = fs.Length;
if (startPoint >= fileLength)
{
result = "文件秒传!";
return result;
}
byte[] data;
System.Net.WebClient webClient = new System.Net.WebClient();
BinaryReader bReader = new BinaryReader(fs);
try
{
#region 续传处理
if (startPoint >= 1 && startPoint < fileLength)
{
fs.Seek(startPoint, SeekOrigin.Current);
}
#endregion
#region 分割文件上传
int step = byteCount;
for (; startPoint < fileLength; startPoint += step)
{
if (startPoint + byteCount > fileLength)
{
step = Convert.ToInt32(fileLength - startPoint);
data = new byte[step];
bReader.Read(data, 0, step);
}
else
{
data = new byte[byteCount];
bReader.Read(data, 0, byteCount);
}
webClient.Headers.Set(HttpRequestHeader.ContentRange, "bytes " + startPoint + "-" + (startPoint + step) + "/" + fs.Length);
webClient.UploadData(hostUrl + "?md5Name=" + md5Str + Path.GetExtension(filePath), "POST", data);
this.BeginInvoke(new ChangeText(ChangeLabelText), (startPoint + step) + "/" + fileLength);
pause:
if (flag == 1)
{
Thread.Sleep(1000);
goto pause;
}
}
#endregion
}
catch (Exception ex)
{
result = ex.Message;
}
finally
{
bReader.Close();
fs.Close();
webClient.Dispose();
}
return result;
}
/// <summary>
/// 上传按钮点击事件。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
filePath = openFileDialog.FileName;
md5Str = GetFileMd5(filePath);
startPoint = GetFileResumePoint(md5Str, Path.GetExtension(filePath));
ThreadStart ts = new ThreadStart(ResumeFileThread);
Thread t = new Thread(ts);
t.Start();
}
}
/// <summary>
/// 暂停按钮点击事件。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
flag = 1;
}
/// <summary>
/// 继续按钮点击事件。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button3_Click(object sender, EventArgs e)
{
flag = 0;
}
/// <summary>
/// 拖拽上传。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Drag(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
MessageBox.Show("准备上传" + files.Length + "个文件!");
foreach (string file in files)
{
MessageBox.Show("开始上传文件:" + file);
filePath = file;
md5Str = GetFileMd5(filePath);
startPoint = GetFileResumePoint(md5Str, Path.GetExtension(filePath));
ThreadStart ts = new ThreadStart(ResumeFileThread);
Thread t = new Thread(ts);
t.Start();
}
}
}
private void Form1_DragDrop(object sender, DragEventArgs e)
{
//Form1_Drag(sender, e);
}
private void Form1_DragOver(object sender, DragEventArgs e)
{
Form1_Drag(sender, e);
}
}
}