您的位置:首页 > 编程语言 > C#

C#实现上传文件分割,断点续传上传文件

2015-06-23 17:47 603 查看
一 介绍

断点续传搜索大部分都是下载的断点续传,涉及到HTTP协议1.1的Range和Content-Range头。

来个简单的介绍

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就支持了。一般断点下载时才用到 Range 和 Content-Range 实体头。

Range

用于请求头中,指定第一个字节的位置和最后一个字节的位置,一般格式:

Range:(unit=first byte pos)-[last byte pos]

Content-Range

用于响应头,指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:

Content-Range: bytes (unit first byte pos) – [last byte pos]/[entity legth]

请求下载整个文件:

GET /test.rar HTTP/1.1
Connection: close
Host: 116.1.219.219
Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头

一般正常回应

HTTP/1.1 200 OK
Content-Length: 801
Content-Type: application/octet-stream
Content-Range: bytes 0-800/801 //801:文件总大小

而今天要说的是上传的断点续传,用到了Content-Range头

上传的续传原理跟下载的续传同理。

就是在上传前把文件拆分后上传。服务器端接收合并,即使上传断了。下次上传依然从服务器端的文件现有字节后合并文件。最终上传完成。

二 实现

服务器端

服务端是webapi实现。或是mvc,webform皆可。

服务端的原理就是接收上传数据流。保存文件。如果此文件已存在。就是合并现有文件。

这里文件的文件名是采用客户端传过来的数据。

文件名称是文件的MD5,保证文件的唯一性。

[HttpGet]

public HttpResponseMessage GetResumFile()

{

//用于获取当前文件是否是续传。和续传的字节数开始点。

var md5str = HttpContext.Current.Request.QueryString["md5str"];

var saveFilePath = HttpContext.Current.Server.MapPath("~/Images/") + md5str;

if(System.IO.File.Exists(saveFilePath))

{

var fs = System.IO.File.OpenWrite(saveFilePath);

var fslength = fs.Length.ToString();

fs.Close();

return new HttpResponseMessage { Content = new StringContent(fslength, System.Text.Encoding.UTF8, "text/plain") };

}

return new HttpResponseMessage(HttpStatusCode.OK);

}

[HttpPost]

public HttpResponseMessage Rsume()

{

var file = HttpContext.Current.Request.InputStream;

var filename = HttpContext.Current.Request.QueryString["filename"];

this.SaveAs(HttpContext.Current.Server.MapPath("~/Images/") + filename, file);

HttpContext.Current.Response.StatusCode = 200;

// For compatibility with IE's "done" event we need to return a result as well as setting the context.response

return new HttpResponseMessage(HttpStatusCode.OK);

}

private void SaveAs(string saveFilePath,System.IO.Stream stream)

{

long lStartPos = 0;

int startPosition = 0;

int endPosition = 0;

var contentRange = HttpContext.Current.Request.Headers["Content-Range"];

//bytes 10000-19999/1157632

if (!string.IsNullOrEmpty(contentRange))

{

contentRange = contentRange.Replace("bytes", "").Trim();

contentRange = contentRange.Substring(0, contentRange.IndexOf("/"));

string[] ranges = contentRange.Split('-');

startPosition = int.Parse(ranges[0]);

endPosition = int.Parse(ranges[1]);

}

System.IO.FileStream fs;

if (System.IO.File.Exists(saveFilePath))

{

fs = System.IO.File.OpenWrite(saveFilePath);

lStartPos = fs.Length;

}

else

{

fs = new System.IO.FileStream(saveFilePath, System.IO.FileMode.Create);

lStartPos = 0;

}

if (lStartPos > endPosition)

{

fs.Close();

return;

}

else if (lStartPos < startPosition)

{

lStartPos = startPosition;

}

else if (lStartPos > startPosition && lStartPos < endPosition)

{

lStartPos = startPosition;

}

fs.Seek(lStartPos, System.IO.SeekOrigin.Current);

byte[] nbytes = new byte[512];

int nReadSize = 0;

nReadSize = stream.Read(nbytes, 0, 512);

while (nReadSize > 0)

{

fs.Write(nbytes, 0, nReadSize);

nReadSize = stream.Read(nbytes, 0, 512);

}

fs.Close();

}

客户端

这里的客户端是winform,功能就是选择文件后即刻上传。如果中途网络,断点等因素没有传成功。

可以再次选择此文件上传。服务器会合并之前传送的文件字节。实现断点续传。

private void btnSelectFile_Click(object sender, EventArgs e)

{

OpenFileDialog openFileDialog = new OpenFileDialog();

openFileDialog.InitialDirectory = "c:\\";

openFileDialog.RestoreDirectory = true;

openFileDialog.FilterIndex = 1;

if (openFileDialog.ShowDialog() == DialogResult.OK)

{

var fName = openFileDialog.FileName;

FileStream fStream = new FileStream(fName, FileMode.Open, FileAccess.Read);

var mdfstr = GetStreamMd5(fStream);

fStream.Close();

var startpoint = isResume(mdfstr, Path.GetExtension(fName));

MessageBox.Show(UpLoadFile(fName, url, 64, startpoint,mdfstr));

}

}

/// <summary>

/// 根据文件名获取是否是续传和续传的下次开始节点

/// </summary>

/// <param name="md5str"></param>

/// <param name="fileextname"></param>

/// <returns></returns>

private int isResume(string md5str, string fileextname)

{

System.Net.WebClient WebClientObj = new System.Net.WebClient();

var url = "http://localhost:13174/api/file/GetResumFile?md5str="+md5str+fileextname;

byte[] byRemoteInfo = WebClientObj.DownloadData(url);

string result = System.Text.Encoding.UTF8.GetString(byRemoteInfo);

if(string.IsNullOrEmpty(result))

{

return 0;

}

return Convert.ToInt32(result);

}

#region

/// <summary>

/// 上传文件(自动分割)

/// </summary>

/// <param name="filePath">待上传的文件全路径名称</param>

/// <param name="hostURL">服务器的地址</param>

/// <param name="byteCount">分割的字节大小</param>

/// <param name="cruuent">当前字节指针</param>

/// <returns>成功返回"";失败则返回错误信息</returns>

public string UpLoadFile(string filePath, string hostURL, int byteCount, long cruuent, string mdfstr)

{

string tmpURL = hostURL;

byteCount = byteCount * 1024;

System.Net.WebClient WebClientObj = new System.Net.WebClient();

FileStream fStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

BinaryReader bReader = new BinaryReader(fStream);

long length = fStream.Length;

string sMsg = "上传成功";

string fileName = filePath.Substring(filePath.LastIndexOf('\\') + 1);

try

{

#region 续传处理

byte[] data;

if (cruuent > 0)

{

fStream.Seek(cruuent, SeekOrigin.Current);

}

#endregion

#region 分割文件上传

for (; cruuent <= length; cruuent = cruuent + byteCount)

{

if (cruuent + byteCount > length)

{

data = new byte[Convert.ToInt64((length - cruuent))];

bReader.Read(data, 0, Convert.ToInt32((length - cruuent)));

}

else

{

data = new byte[byteCount];

bReader.Read(data, 0, byteCount);

}

try

{

//*** bytes 21010-47021/47022

WebClientObj.Headers.Remove(HttpRequestHeader.ContentRange);

WebClientObj.Headers.Add(HttpRequestHeader.ContentRange, "bytes " + cruuent + "-" + (cruuent + byteCount) + "/" + fStream.Length);

hostURL = tmpURL + "?filename=" + mdfstr + Path.GetExtension(fileName);

byte[] byRemoteInfo = WebClientObj.UploadData(hostURL, "POST", data);

string sRemoteInfo = System.Text.Encoding.Default.GetString(byRemoteInfo);

// 获取返回信息

if (sRemoteInfo.Trim() != "")

{

sMsg = sRemoteInfo;

break;

}

}

catch (Exception ex)

{

sMsg = ex.ToString();

break;

}

#endregion

}

}

catch (Exception ex)

{

sMsg = sMsg + ex.ToString();

}

try

{

bReader.Close();

fStream.Close();

}

catch (Exception exMsg)

{

sMsg = exMsg.ToString();

}

GC.Collect();

return sMsg;

}

public static string GetStreamMd5(Stream stream)

{

var oMd5Hasher = new MD5CryptoServiceProvider();

byte[] arrbytHashValue = oMd5Hasher.ComputeHash(stream);

//由以连字符分隔的十六进制对构成的String,其中每一对表示value 中对应的元素;例如“F-2C-4A”

string strHashData = BitConverter.ToString(arrbytHashValue);

//替换-

strHashData = strHashData.Replace("-", "");

string strResult = strHashData;

return strResult;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: