• .net 实现上传文件分割,断点续传上传文件


    一 介绍

    断点续传搜索大部分都是下载的断点续传,涉及到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]

    请求下载整个文件:

    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;
     }
    

      

  • 相关阅读:
    Android开发环境
    安卓学习
    Shuffle'm Up POJ
    Duizi and Shunzi HDU
    Find a path HDU
    Cyclic Nacklace HDU
    Keywords Search HDU
    HDU 1495 非常可乐
    J
    Fire Game FZU
  • 原文地址:https://www.cnblogs.com/qiao-xi/p/6217138.html
Copyright © 2020-2023  润新知