zoukankan      html  css  js  c++  java
  • 使用HttpWebRequest实现大文件上传

    Author:xuzhihong

    Create Date:2011-06-03

    Descriptions: WinForm程序使用HttpWebRequest实现大文件上传

    概述:

    通常在WinForm程序中都是采用WebClient方式实现文件上传功能,本身这个方式没有问题,但是当需要上传大文件比如说(300+M)的时候,那么WebClient将会报内存不足异常(Out of Memory Exceptions),究其原因是因为WebClient方式是一次性将整个文件全部读取到本地内存中,然后再以数据流形式发送至服务器。本文将讲述如何采用HttpWebRequest方式每次读取固定大小数据片段(如4KB)发送至服务器,为大文件上传提供解决方案,本文还将详细讲述将如何将“文件上传”功能做为用户自定义控件,实现模块重用。

     

    关键词:HttpWebRequestWebClientOutOfMemoryExceptions

     

    解决方案:

    开始我在WinForm项目中实现文件上传功能的时候,是采用WebClientWebClient myWebClient = new WebClient();)方式,这大部分情况都是正确的,但有时候会出现内存不足的异常(Out of Memory Exceptions),经常测试,发现是由于上传大文件的时候才导致这问题。在网上查阅了一下其他网友的解决方案,最后找的发生异常的原因:“WebClient方式是一次性将整个文件全部读取到本地内存中,然后再以数据流形式发送至服务器”,详细请参考:http://blogs.msdn.com/b/johan/archive/2006/11/15/are-you-getting-outofmemoryexceptions-when-uploading-large-files.aspx 按照这个解释,那么大文件上传出现内存不足的异常也就不足为奇了。下面我将讲述如何一步步使用HttpWebRequest方式来实现文件分块上传数据流至服务器。

    按照惯例还是先预览一下文件上传最后的效果吧,如下图所示:

    使用HttpWebRequest实现大文件上传 - 飞天心宏 - 飞天心宏的博客

    界面分为两部分,上面是文件基本信息,下面是文件上传自定义控件,我这里实现的是一个案件上传多个监控视频功能。以下是详细步骤:

    第一步:创建用户自定义控件BigFileUpload.xaml

    文件上传是一个非常常用的功能,为了所写的程序能非常方便地多次重复使用,我决定将其处理为一个用户自定义控件(UserControl)。

    我们先在项目中创建一个FileUpload文件夹,在其目录下新建一个WPF自定义控件文件命名为BigFileUpload.xaml,这样就表示文件上传是一个独立的小模块使用。之所以用WPF自定义控件是因为WPF页面效果好看点,而且我想以后可能大部分C/S程序都会渐渐的由WinForm转向WPF吧,当然创建Window Forms用户控件也是没有问题的。然后我们需要做一个下图效果的页面布局:

    使用HttpWebRequest实现大文件上传 - 飞天心宏 - 飞天心宏的博客

    前台设计代码如下:

    <UserControl x:Class="CHVM.FileUpload.BigFileUpload"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Height="160" Width="480">

        <Grid Height="160" Width="480" Background="White">

            <Label Height="28" HorizontalAlignment="Left" Margin="16,10,0,0" Name="label1" VerticalAlignment="Top" Width="53">文件</Label>

            <Label HorizontalAlignment="Left" Margin="15,52,0,80" Name="label2" Width="54">进度</Label>

            <ProgressBar Height="20" Margin="61,52,116,0" Name="progressBar1" VerticalAlignment="Top" />

            <TextBox Height="23" Margin="61,12,116,0" Name="txtBoxFileName" VerticalAlignment="Top"  />

            <Button Height="23" HorizontalAlignment="Right" Margin="0,10,35,0" Name="BtnBrowse" VerticalAlignment="Top" Width="75"Click="BtnBrowse_Click">浏览...</Button>

            <Button Height="23" HorizontalAlignment="Right" Margin="0,52,35,0" Name="BtnUpload" VerticalAlignment="Top" Width="75"Click="BtnUpload_Click">上传</Button>

            <Label HorizontalAlignment="Left" Margin="16,0,0,44" Name="lblState" Width="183" Height="35"VerticalAlignment="Bottom">已上传</Label>

            <Label Margin="231,0,35,44" Name="lblSize" Height="35" VerticalAlignment="Bottom">/</Label>

            <Label Height="28" HorizontalAlignment="Left" Margin="16,0,0,10" Name="lblTime" VerticalAlignment="Bottom"Width="183">已用时</Label>

            <Label Height="28" Margin="230,0,35,10" Name="lblSpeed" VerticalAlignment="Bottom">平均速度</Label>

        </Grid>

    </UserControl>

     

     

    后台CS代码:

    public delegate void FilUploadHandler(EventFileUploadArg e);

        /// <summary>

        /// 自定义事件数据参数类

        /// </summary>

        public class EventFileUploadArg : EventArgs

        {

            private HttpWebRequestReturn hwr;

            /// <summary>

            /// 文件上传服务器返回类

            /// </summary>

            public HttpWebRequestReturn HwrReturn

            {

                get

                {

                    return hwr;

                }

                set

                {

                    hwr = value;

                }

            }

            public EventFileUploadArg()

            {

                hwr = new HttpWebRequestReturn();

            }

            public EventFileUploadArg(HttpWebRequestReturn hwrReturn)

            {

                hwr = hwrReturn;

            }

        }

     

        /// <summary>

        /// BigFileUpload.xaml 的交互逻辑

        /// </summary>

        public partial class BigFileUpload : UserControl

        {

            public BigFileUpload()

            {

                InitializeComponent();

            }

     

            public event FilUploadHandler EventFileUpload;

     

            /// <summary>

            /// 服务器接收的地址 如:http://192.168.0.105:8078/Default.aspx

            /// </summary>

            public string ServerAddress

            {

                get;

                set;

            }

            /// <summary>

            /// 状态标识是否上传成功

            /// </summary>

            private bool IsSuccess

            {

                get;

                set;

            }

           

            /// <summary>

            /// 将本地文件上传到指定的服务器(HttpWebRequest方法)

            /// </summary>

            /// <param name="address">文件上传到的服务器</param>

            /// <param name="fileNamePath">要上传的本地文件(全路径)</param>

            /// <param name="saveName">文件上传后的名称</param>

            /// <param name="progressBar">上传进度条</param>

            /// <returns>服务器反馈信息</returns>

            private HttpWebRequestReturn Upload_Request(string addressstring fileNamePathstring saveNameProgressBarprogressBar)

            {

                HttpWebRequestReturn hwr;

     

                // 要上传的文件

                FileStream fs = new FileStream(fileNamePathFileMode.OpenFileAccess.Read);

                BinaryReader r = new BinaryReader(fs);

     

                //时间戳

                string strBoundary = "----------" + DateTime.Now.Ticks.ToString("x");

                byte[] boundaryBytes = Encoding.ASCII.GetBytes(" --" + strBoundary + " ");

     

                //请求头部信息

                StringBuilder sb = new StringBuilder();

                sb.Append("--");

                sb.Append(strBoundary);

                sb.Append(" ");

                sb.Append("Content-Disposition: form-data; name="");

                sb.Append("file");

                sb.Append(""; filename="");

                sb.Append(saveName);

                sb.Append(""");

                sb.Append(" ");

                sb.Append("Content-Type: ");

                sb.Append("application/octet-stream");

                sb.Append(" ");

                sb.Append(" ");

     

                string strPostHeader = sb.ToString();

                byte[] postHeaderBytes = Encoding.UTF8.GetBytes(strPostHeader);

     

                // 根据uri创建HttpWebRequest对象

                HttpWebRequest httpReq = (HttpWebRequest)WebRequest.Create(new Uri(address));

                httpReq.Method = "POST";

     

                //对发送的数据不使用缓存【重要、关键】

                httpReq.AllowWriteStreamBuffering = false;

     

                //设置获得响应的超时时间(300秒)

                httpReq.Timeout = 300000;

                httpReq.ContentType = "multipart/form-data; boundary=" + strBoundary;

                long length = fs.Length + postHeaderBytes.Length + boundaryBytes.Length;

                long fileLength = fs.Length;

                httpReq.ContentLength = length;

                try

                {

                    progressBar.Maximum = fileLength;//int.MaxValue;

                    progressBar.Minimum = 0;

                    progressBar.Value = 0;

     

                    //每次上传4k

                    int bufferLength = 4096;

                    byte[] buffer = new byte[bufferLength];

     

                    //已上传的字节数

                    long offset = 0;

     

                    //开始上传时间

                    DateTime startTime = DateTime.Now;

                    int size = r.Read(buffer, 0, bufferLength);

                    Stream postStream = httpReq.GetRequestStream();

     

                    //发送请求头部消息

                    postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);

                    while (size > 0)

                    {

                        postStream.Write(buffer, 0, size);

                        offset += size;

                        progressBar.Value = offset;//(int)(offset * (int.MaxValue / length));

                        TimeSpan span = DateTime.Now - startTime;

                        double second = span.TotalSeconds;

                        lblTime.Content = "已用时:" + second.ToString("F2") + "";

                        if (second > 0.0001)

                        {

                            lblSpeed.Content = 平均速度:" + (offset / 1024 / second).ToString("0.00") + "KB/";

                        }

                        else

                        {

                            lblSpeed.Content = 平均速度太快,系统放弃计算";

                        }

                        //lblState.Content = "已上传:" + (offset * 100.0 / length).ToString("F2") + "%";

                        lblState.Content = "已上传:" + (offset * 100.0 / fileLength).ToString("F2") + "%";

                        //1024*1024=1048576

                        if (fileLength > 1048576) //根据文件是否大于1M,来使用单位【处理精度】

                        {

                            lblSize.Content = (offset / 1048576.0).ToString("F2") + "M/" + (fileLength / 1048576.0).ToString("F2") + "M";

                        }

                        else

                        {

                            lblSize.Content = (offset / 1024.0).ToString("F2") + "KB/" + (fileLength / 1024.0).ToString("F2") +"KB";

                        }

                       

                        size = r.Read(buffer, 0, bufferLength);

                    }

                    //添加尾部的时间戳

                    postStream.Write(boundaryBytes, 0, boundaryBytes.Length);

                    postStream.Close();

     

                    //获取服务器端的响应

                    WebResponse webRespon = httpReq.GetResponse();

                    Stream s = webRespon.GetResponseStream();

                    StreamReader sr = new StreamReader(s);

     

                    //读取服务器端返回的消息

                    string serverMsg = sr.ReadLine();

                    hwr = JSSerialize.Deserialize<HttpWebRequestReturn>(serverMsg);

                    s.Close();

                    sr.Close();

     

                }

                catch(Exception ex)

                {

                    hwr = new HttpWebRequestReturn();

                    hwr.success = false;

                    hwr.errors = ex.Message;

                }

                finally

                {

                    fs.Close();

                    r.Close();

                }

     

                return hwr;

            }

     

            /// <summary>

            /// 浏览

            /// </summary>

            /// <param name="sender"></param>

            /// <param name="e"></param>

            private void BtnBrowse_Click(object senderRoutedEventArgs e)

            {

                IsSuccess = false;

                System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();

                ofd.Multiselect = false//单选

                ofd.Filter = "Video files (*.avi)|*.avi|All files (*.*)|*.*";

                ofd.FilterIndex = 2;

                ofd.RestoreDirectory = false;

                if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)

                {

                   txtBoxFileName.Text = ofd.FileName;

                }

            }

     

            /// <summary>

            /// 上传

            /// </summary>

            /// <param name="sender"></param>

            /// <param name="e"></param>

            private void BtnUpload_Click(object senderRoutedEventArgs e)

            {

                BtnUpload.IsEnabled = false;           

                string fileNamePath = txtBoxFileName.Text//本地欲上传文件完整路径

                if (fileNamePath == "")

                {

                    MessageBox.Show("请选择要上传的文件路径!","温馨提示");

                }

                else if (!File.Exists(fileNamePath))

                {

                    MessageBox.Show("选择的文件不存在,可能已经被删除,请重新选择!""温馨提示");

                }

                else

                {

                    try

                    {

                        string fileName = fileNamePath.Substring(fileNamePath.LastIndexOf("\") + 1); //欲上传文件名

                        string fileNameExt = fileName.Substring(fileName.LastIndexOf(".")); //文件后缀,包含"."

                        string saveName = fileName.Substring(0, fileName.Length - fileNameExt.Length) +DateTime.Now.ToString("yyMMddhhmmss") + DateTime.Now.Millisecond.ToString() + fileNameExt;

                        HttpWebRequestReturn hwr = Upload_Request(ServerAddressfileNamePathsaveName,progressBar1);                   

                        if (hwr.success//上传成功

                        {

                            if (EventFileUpload != null)

                            {

                                EventFileUploadArg arg = new EventFileUploadArg(hwr);

                                EventFileUpload(arg); //上传后执行文件上传的后续的自定义事件

                            }

                        }

                        else

                        {

                            MessageBox.Show(hwr.message);

                        }

                       

                    }

                    catch (System.Exception ex)

                    {

                        MessageBox.Show(ex.Message);

                    }

                }

                BtnUpload.IsEnabled = true;

            }

     

    曾经在大学的时候,记得数字图像处理老师给我们说过:“中国的书籍讲的大部分都是理论,很少有真正将完整代码写出来的”。所以我每次写文章的时候,都有个习惯就是尽可能完整的把代码贴出来,一是怕自己文字功底太差表示不清楚,二是方便大家和自己以后理解。题外话少说,还是简单的讲述一下界面及代码结构吧。

    界面相当简单,就是一个浏览按钮和一个上传按钮,以及一些用于增加友好度的Label提示。浏览按钮对应的事件BtnBrowse_Click,里面定义了一个OpenFileDialog用于选择需要上传的文件。上传按钮对应的事件BtnUpload_Click作了一些基本的验证,然后调用了最关键的Upload_Request方法,同时执行了一个委托事件EventFileUpload(arg); //上传后执行文件上传的后续的自定义事件

    Uplaod_Request方法带有四个参数:

     /// <summary>

     /// 将本地文件上传到指定的服务器(HttpWebRequest方法)

     /// </summary>

     /// <param name="address">文件上传到的服务器(服务器接收的地址如:http://192.168.0.105:8078/Default.aspx )</param>

     /// <param name="fileNamePath">要上传的本地文件(全路径)</param>

     /// <param name="saveName">文件上传后的名称</param>

     /// <param name="progressBar">上传进度条</param>

    /// <returns>服务器反馈信息</returns>

    private HttpWebRequestReturn Upload_Request(string addressstring fileNamePathstring saveNameProgressBar progressBar){}

    值得一提的是这里的返回类型HttpWebRequestReturn(点击查看定义)是为了和数据库对应自己定义的一个类,继承自统一返回类型TwiReturn(点击查看定义),里面记录了文件服务器反馈的综合信息。

     

    第二步:创建服务器响应程序BigFileUploadServerApp

    很显然文件上传至服务器后需要有个对应的响应程序。那么我们再创建一个单独的Web应用程序(命名为:BigFileUploadServerApp),发布在服务器中的IIS上,只需要一个默认的Default.aspx页面和一个FileUpload空文件夹即可,我们将FileUpload文件夹所存放的目录作为文件上传至服务器存放的目录。

    Default.aspx.cs代码也相当简单:

            protected void Page_Load(object senderEventArgs e)

            {

                HttpWebRequestReturn hwr = new HttpWebRequestReturn();

                hwr.hasRight = true;

                if (Request.Files.Count > 0)

                {               

                    try

                    {

                        HttpPostedFile file = Request.Files[0];

                        string filePath = this.MapPath("FileUpload") + "\" + file.FileName;

                        file.SaveAs(filePath);

                        hwr.FileName = file.FileName;

                        hwr.FileFullName = filePath;

                        hwr.ContentLength = file.ContentLength;

                        IPHostEntry hostInfo  = Dns.GetHostEntry(Server.MachineName);

                        hwr.ServerIP = hostInfo.AddressList[0].ToString();

                        hwr.success = true;

                    }

                    catch (Exception ex)

                    {

                        hwr.errors = ex.Message;

                    }

                }

                else

                {

                    hwr.errors = "服务器没接收到上传的文件信息,请检查上传的文件是否为空文件!";

                }

                string strReturn = JSSerialize.Serialize(hwr);

                Response.Write(strReturn);

                Response.End();

            }

    返回类型记录了另存为的文件名FileName,文件在服务器中的全路径FileFullName,服务器IP地址ServerIP等信息,JSSerialize.Serialize()(点击查看定义)方法是将对象序列化为字符串。最后需要说明的是:微软为了防止拒绝服务攻击,对文件上传做了一个大小限制,最大默认为4M,然后我们使用HttpWebRequest方法将会受到其影响。为了突破这个限制,那么我们需要在Web.Config文件中的system.web节点下增加一个httpRuntime配置,

      <system.web>

        <httpRuntime maxRequestLength="1000000" executionTimeout="600"></httpRuntime>

      </system.web>

    其中MaxRequestLength单位为KB,executionTimeout单位为秒,大小自己根据实际情况进行控制。

    文件上传至服务器之后,我们还需要将文件基本信息记录到对应的数据库中,那么在执行“上传”事件时我们还需要执行自定义后续操作。由于我们做的是一个通用的文件上传功能,所以不能直接将业务逻辑写在BtnUpload_Click方法中,因为每个地方上传处理的逻辑也许并不一样。这个时候当然就该是伟大的委托上场了,在此我们定义了一个FileUploadHandler委托,定义如下:

    public delegate void FilUploadHandler(EventFileUploadArg e);

    其参数有点特别,不是常规的EventArgs,而是自定义继承自EventArgsEventFileUploadArg,定义如下:

        /// <summary>

        /// 自定义事件数据参数类

        /// </summary>

        public class EventFileUploadArg : EventArgs

        {

            private HttpWebRequestReturn hwr;

            /// <summary>

            /// 文件上传服务器返回类

            /// </summary>

            public HttpWebRequestReturn HwrReturn

            {

                get

                {

                    return hwr;

                }

                set

                {

                    hwr = value;

                }

            }

            public EventFileUploadArg()

            {

                hwr = new HttpWebRequestReturn();

            }

            public EventFileUploadArg(HttpWebRequestReturn hwrReturn)

            {

                hwr = hwrReturn;

            }

        }

    为什么要定义这么一个参数呢?因为我们在服务器接收文件后得到了一些反馈信息(是一个HttpWebRequestReturn类的实例),那么在处理后续的逻辑的时候,是希望了解这些信息的,所谓的了解其实就是能够访问反馈信息,那么无疑于这种方式公开出来是非常合理的。

     

    第三步:应用

    到这里我们已经把自定义用户控件做好了,但是还没真正使用。这么这一步我们将讨论如何使用它。为了实现前面演示的效果我们新建一个WinForm窗体页面暂且命名为(FormVideoFileUpload.cs),然后做一个简单的布局,如下图:

    使用HttpWebRequest实现大文件上传 - 飞天心宏 - 飞天心宏的博客

    上面的都是文件基本信息,下面的是一个Panel用于承载我们前面做好的“自定义文件上传控件BigFileUpload.xaml”,后台cs代码如下:

    public partial class FormVideoFileUpload : Form

        {

            public FormVideoFileUpload()

            {

                InitializeComponent();

     

                AddBfuControl();

            }

     

            /// <summary>

            /// 增加文件上传自定义控件

            /// </summary>

            public void AddBfuControl()

            {

                BigFileUpload bfu = new BigFileUpload();

                bfu.EventFileUpload += new FilUploadHandler(Bfu_BtnUpload_Click);

                bfu.ServerAddress = CommPar.VM_VideoFilesUrl;

                ElementHost elHost = new ElementHost();

                elHost.Dock = DockStyle.None;

                elHost.Width = panel1.Width;

                elHost.Height = panel1.Height;

                elHost.Child = bfu;

                panel1.Controls.Add(elHost);           

            }

     

            /// <summary>

            /// 文件上传完成的自定义事件

            /// </summary>

            /// <param name="arg"></param>

            public void Bfu_BtnUpload_Click(EventFileUploadArg arg)

            {

                if (arg.HwrReturn.success)

                {

                    TMEDIAS medias = new TMEDIAS();

                    medias.MEDIASEED = txtMediaSeed.Text;

                    medias.MEDIASOURCE = txtMediaSource.Text;

                    medias.CASENUMBER = txtCaseNumber.Text;

                    medias.REMARK = txtRemark.Text;

                    medias.FILENAME = arg.HwrReturn.FileName;

                    medias.FILEFULLNAME = arg.HwrReturn.FileFullName;

                    medias.SERVERIP = arg.HwrReturn.ServerIP;

                    medias.UPDATETIME = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

                    medias.OPERATORID = 6;

                    medias.OPERATOR = "赵精伟";

                    TwiReturn twi = UsingBLL.medias.Add(medias);

                    if (twi.success)

                    {

                        DialogResult dResult = MessageBox.Show("恭喜你文件上传成功,是否继续上传视频文件?""恭喜",MessageBoxButtons.YesNoMessageBoxIcon.Question);

                        if (dResult == DialogResult.Yes)

                        {

                            panel1.Controls.Clear();

                            AddBfuControl();

                        }

                        else

                        {

                            this.Hide();

                        }

                    }

                    else

                    {

                        MessageBox.Show(twi.message"提示");

                    }             

                   

                }

                else

                {

                    MessageBox.Show(arg.HwrReturn.message,"提示");

                }

            }

    }

     

     

    经过不懈的努力,和这么长时间的耐心,到这里已经完成了我们所要做的工作了,看看我们的功能界面吧,不容易呀!

    使用HttpWebRequest实现大文件上传 - 飞天心宏 - 飞天心宏的博客

    这里有个提示框提示用户是否继续上传,如果是那么程序将刷新一下用户控件,但是上面的文件案件基本信息仍然保留,这样就做到了我所希望的一个案件对应上传多个视频的效果。

     

     

    说在最后

    该解决方案成功实现了基于HttpWebRequest的方式实现大文件上传,相对来说这个界面还是挺好看的。对应大文件上传有人也许会说用FTP的方式处理,听人说配置有点复杂,由于我个人比较懒,所以没去亲自试验,以后有机会再试试FTP的方式,只有我亲自试成功了,我才会写出来。

    最后,由于个人技术水平和写作能力的限制,文章有不足之处再所难免,还望大家批评指正,如果发现问题,我也将会尽快修改。知错、认错、改错。

     

  • 相关阅读:
    mysql 主从架构搭建
    tomcat+nginx 反向代理 动静分离
    ELK(Elasticsearch + Logstash + Kibana)安装部署对nginx的日志收集
    Kibana server is not ready yet出现的原因
    apache+tomcat+单机双实例+动静分离+负载均衡
    docker的基本安装和简单使用+Dockerfile常用指令
    LVS+keepalived+DR 负载均衡高可用
    md5sum摘要
    python爬虫
    python和数据库
  • 原文地址:https://www.cnblogs.com/zhangxiaozhong/p/3232177.html
Copyright © 2011-2022 走看看