在C#中部分下载和序列化大文件?

时间:2012-02-20 17:35:32

标签: c# serialization download

作为我大学即将开展的项目的一部分,我需要编写一个客户端,从服务器下载媒体文件并将其写入本地磁盘。由于这些文件可能非常大,我需要实现部分下载和序列化,以避免过多的内存使用。

我想出了什么:

namespace PartialDownloadTester
{
    using System;
    using System.Diagnostics.Contracts;
    using System.IO;
    using System.Net;
    using System.Text;

    public class DownloadClient
    {
        public static void Main(string[] args)
        {
            var dlc = new DownloadClient(args[0], args[1], args[2]);
            dlc.DownloadAndSaveToDisk();
            Console.ReadLine();
        }

        private WebRequest request;

        // directory of file
        private string dir;

        // full file identifier
        private string filePath;

        public DownloadClient(string uri, string fileName, string fileType)
        {
            this.request = WebRequest.Create(uri);
            this.request.Method = "GET";
            var sb = new StringBuilder();
            sb.Append("C:\\testdata\\DownloadedData\\");
            this.dir = sb.ToString();
            sb.Append(fileName + "." + fileType);
            this.filePath = sb.ToString();
        }

        public void DownloadAndSaveToDisk()
        {
            // make sure directory exists
            this.CreateDir();

            var response = (HttpWebResponse)request.GetResponse();
            Console.WriteLine("Content length: " + response.ContentLength);
            var rStream = response.GetResponseStream();
            int bytesRead = -1;
            do
            {
                var buf = new byte[2048];
                bytesRead = rStream.Read(buf, 0, buf.Length);
                rStream.Flush();
                this.SerializeFileChunk(buf);
            }
            while (bytesRead != 0);
        }

        private void CreateDir()
        {
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
        }

        private void SerializeFileChunk(byte[] bytes)
        {
            Contract.Requires(!Object.ReferenceEquals(bytes, null));
            FileStream fs = File.Open(filePath, FileMode.Append);
            fs.Write(bytes, 0, bytes.Length);
            fs.Flush();
            fs.Close();
        }
    }
}

出于测试目的,我使用了以下参数:

  

“http://itu.dk/people/janv/mufc_abc.jpg”“mufc_abc”“jpg”

然而,即使内容长度打印63780(即图像的实际尺寸),图片仍然不完整(只有第一个~10%看起来正确)。

所以我的问题是:

  1. 这是部分下载和序列化的正确方法还是有更好/更简单的方法?
  2. 响应流的完整内容是否存储在客户端内存中?如果是这种情况,我是否需要使用HttpWebRequest.AddRange从服务器部分下载数据以节省我的客户端内存?
  3. 为什么序列化失败了,我得到了破碎的图像?
  4. 使用FileMode.Append时是否会引入大量开销? (msdn声明此选项“寻找文件的末尾”)
  5. 提前致谢

3 个答案:

答案 0 :(得分:3)

您绝对可以使用WebClient简化代码:

class Program
{
    static void Main()
    {
        DownloadClient("http://itu.dk/people/janv/mufc_abc.jpg", "mufc_abc.jpg");
    }

    public static void DownloadClient(string uri, string fileName)
    {
        using (var client = new WebClient())
        {
            using (var stream = client.OpenRead(uri))
            {
                // work with chunks of 2KB => adjust if necessary
                const int chunkSize = 2048;
                var buffer = new byte[chunkSize];
                using (var output = File.OpenWrite(fileName))
                {
                    int bytesRead;
                    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        output.Write(buffer, 0, bytesRead);
                    }
                }
            }
        }
    }
}

注意我是如何只将我实际从套接字读取的字节数写入输出文件而不是整个2KB缓冲区。

答案 1 :(得分:0)

以下是Microsoft的解决方案:http://support.microsoft.com/kb/812406

答案 2 :(得分:0)

我不知道这是否是问题的根源,但是我会像这样改变循环

const int ChunkSize = 2048;
var buf = new byte[ChunkSize];
var rStream = response.GetResponseStream();
do {
    int bytesRead = rStream.Read(buf, 0, ChunkSize);
    if (bytesRead > 0) {
        this.SerializeFileChunk(buf, bytesRead);
    }
} while (bytesRead == ChunkSize);

serialize方法将获得一个额外的参数

private void SerializeFileChunk(byte[] bytes, int numBytes)

然后写入正确的字节数

fs.Write(bytes, 0, numBytes);

更新:

我不认为每次都需要关闭和重新打开文件。我也会使用using语句来关闭资源,即使应该发生异常也是如此。 using语句最后调用资源的Dispose()方法,在文件流的情况下调用Close()using可以应用于实施IDisposable的所有类型。

var buf = new byte[2048];
using (var rStream = response.GetResponseStream()) {
    using (FileStream fs = File.Open(filePath, FileMode.Append)) {
        do {
            bytesRead = rStream.Read(buf, 0, buf.Length);
            fs.Write(bytes, 0, bytesRead);
        } while (...);
    }
}

using语句就是这样的

{
    var rStream = response.GetResponseStream();
    try
    {
        // do some work with rStream here.
    } finally {
        if (rStream != null) {
            rStream.Dispose();
        }
    }
}