使用MemoryStream下载大文件

4

我正在编写一个通用处理程序,用于从安全的FTP服务器下载相当大(400+ MB)的文件。通过将响应流复制到MemoryStream,然后二进制写入字节数组,我已经让我的代码在小型测试图像上运行成功。

我的代码如下(DownloadFile.ashx):

// Set correct path
string path = ftpHelper.GetCompletePath();
path += "/" + loginId + "/" + folderName + "/" + fileName;

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(path);
request.UsePassive = false;
request.Credentials = new NetworkCredential(ftpHelper.Username, ftpHelper.Password);

byte[] fileBytes = null;

using (var response = (FtpWebResponse)request.GetResponse())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        using (var memoryStream = new MemoryStream())
        {
            if (responseStream != null) responseStream.CopyTo(memoryStream);
            fileBytes = memoryStream.ToArray();
        }
    }
 }

if (fileBytes.Length > 0)
{
     context.Response.AppendHeader("Content-Length", fileBytes.Length.ToString());
     context.Response.BinaryWrite(fileBytes);
}

request.Abort();

context.ApplicationInstance.CompleteRequest();

问题是:在一个具有8GB内存的活动Web服务器上(由于这是一个相当大的网站,它目前使用其现有内存的约60%!它可能需要升级内存 ;-)),使用CopyTo()和MemoryStream处理如此大的文件是否安全?
我知道我可以通过ftp://username:password@path.com直接将下载链接设置到客户端上,但FTP上的内容受到密码保护,因为其中包含一些敏感数据。
因此,是否使用MemoryStream处理如此大的文件是安全的?如果不是:是否有其他我忽略的方法来处理此问题?
提前感谢 :-)
3个回答

4
MemoryStream可以安全地用于处理大文件。但是,它将把整个文件加载到内存中,并在垃圾回收确定适当时间回收该内存之前一直存在。
8GB的RAM对于“中等”负载的生产服务器来说已经足够了。当然,这是一个客观的评估标准,但如果单个低到中等流量的WebApp使用超过8GB的RAM,则应重新考虑某些设计决策。
有两种选项可以避免将整个远程文件加载到内存中:
  1. 将其写入到本地磁盘上的文件中,并将该文件返回给WebApp客户端。
  2. 直接对响应流进行分块写入。
例如,选项2可能如下所示:
//...
using (Stream responseStream = response.GetResponseStream())
{
    Response.BufferOutput= false;   // to prevent buffering 
    byte[] buffer = new byte[1024]; 
    int bytesRead = 0; 
    while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)  
    { 
         Response.OutputStream.Write(buffer, 0, bytesRead); 
    }
}
//...

谢谢你的答案和代码示例!我复制了你的代码,它可以直接运行 - 而且速度非常快! :-) 使用我的CopyTo()内存流逻辑,实际开始下载图像需要一秒钟左右。 - bomortensen
我并没有期望它会“顺利运行”,这是一个不错的奖励!我会调查修改缓冲区大小的可能性。你可以调整它以匹配来自FTP服务器的吞吐量;这可能会对操作的整体速度产生另一种影响。 - Xenolightning
当我使用Firefox以二进制模式打开文件时,如何确保开始下载? - Hadi Ranji

0
    void WriteFile(string path, string filename)
    {
        using (FileStream fs = File.OpenRead(path))
        {
            //response is HttpListenerContext.Response... 
            Response.ContentType = System.Net.Mime.MediaTypeNames.Application.Octet;
            Response.AddHeader("Content-disposition", "attachment; filename=" + filename); 
            byte[] buffer = new byte[64 * 1024];
            int read; 
            while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
            {
                Response.OutputStream.Write(buffer, 0, read);
                Response.OutputStream.Flush(); //seems to have no effect
            } 
            Response.OutputStream.Close();
        }
        File.Delete(path);
        Response.End();
    }

0

使用内存流应该是可以的。记住你不需要一次性写入整个文件,保持写入数据直到全部返回,然后关闭响应。

看看这个问题,以获取如何分块返回天数的一些想法在C#中以小块下载大文件


非常感谢您的快速回复,Ian :-) 我也找到了这个:http://support.microsoft.com/default.aspx?scid=kb;en-us;812406(页面中间的C#示例)。所以基本上我可以像这样跳过内存流吗?我会试一下的! - bomortensen
@bomortensen 是的,通常任何流都可以,只需一次写入几个位即可。 - Ian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接