.net 實現上傳文件分割,斷點續傳上傳文件

一 介紹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]指針

請求下載整個文件:

  1. GET /test.rar HTTP/1.1
  2. Connection: close
  3. Host: 116.1.219.219
  4. Range: bytes=0-801 //通常請求下載整個文件是bytes=0- 或不用這個頭

通常正常回應

  1. HTTP/1.1 200 OK
  2. Content-Length: 801
  3. Content-Type: application/octet-stream
  4. 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;
 }
相關文章
相關標籤/搜索