作为我大学即将开展的项目的一部分,我需要编写一个客户端,从服务器下载媒体文件并将其写入本地磁盘。由于这些文件可能非常大,我需要实现部分下载和序列化,以避免过多的内存使用。
我想出了什么:
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%看起来正确)。
所以我的问题是:
提前致谢
答案 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();
}
}
}