一 介紹web
斷點續傳搜索大部分都是下載的斷點續傳,涉及到HTTP協議1.1的Range和Content-Range頭。api
來個簡單的介紹服務器
所謂斷點續傳,也就是要從文件已經下載的地方開始繼續下載。在之前版本的 HTTP 協議是不支持斷點的,HTTP/1.1 開始就支持了。通常斷點下載時纔用到 Range 和 Content-Range 實體頭。網絡
Rangemvc
用於請求頭中,指定第一個字節的位置和最後一個字節的位置,通常格式:app
Range:(unit=first byte pos)-[last byte pos]ide
Content-Rangethis
用於響應頭,指定整個實體中的一部分的插入位置,他也指示了整個實體的長度。在服務器向客戶返回一個部分響應,它必須描述響應覆蓋的範圍和整個實體長度。通常格式:url
Content-Range: bytes (unit first byte pos) – [last byte pos]/[entity legth]指針
請求下載整個文件:
通常正常回應
而今天要說的是上傳的斷點續傳,用到了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; }