使用System.IO.Packaging.Package进行文件压缩所产生的问题

最近在项目中需要进行文件压缩,即将打包好的压缩文件提供给用户,用户进行下载。

获知微软提供了一个System.IO.Packaging.Package的类,从而可以进行打包,那么我就进行了使用。谁知道,这一用就用出事了。

首先看代码吧。

结构并不复杂,vs2010+mvc3,直接写在action里。(例子是这样写的)需要注意的是,System.IO.Packaging.Package是在WindowsBase.dll里的。

private const long BUFFER_SIZE = 4096;
public ActionResult DownLoad(int id)
{
//实际情况中,此处将会根据id从数据库中获得数据列表
//此列表中将包括需要压缩的文件名
try
{
byte[] file = null;
//假设,有一个download目录,作为下载目录,其中包含若干文件
string folder = Server.MapPath("~/download/");
DirectoryInfo f
= new DirectoryInfo(folder);
using (MemoryStream ms = new MemoryStream())
{
using (Package zip = Package.Open(ms, FileMode.Create))//使用内存流打开
{
//在实际情况中,此处将会是一个循环,从数据库中获得一批需要进行压缩的文件路径
//获得相对根路径
Uri uri = PackUriHelper.CreatePartUri(new Uri("文件路径", UriKind.Relative));
if (zip.PartExists(uri))//如果此文件已存在,删掉
{
zip.DeletePart(uri);
}
PackagePart part
= zip.CreatePart(uri, "", CompressionOption.Normal);
//使用文件流打开需要压缩的文件
using (FileStream fileStream = new FileStream(folder + "文件路径", FileMode.Open, FileAccess.Read))
{
using (Stream dest = part.GetStream())
{
//将数据复制到stream中
CopyStream(fileStream, dest);
}
}
//此处对应循环结束
}
file
= ms.ToArray();
}
return File(file, "application/x-zip-compressed", "myFile.zip");
}
catch (Exception ex)
{

return View("Error");
}
}

private static void CopyStream(System.IO.Stream inputStream, System.IO.Stream outputStream)
{
long bufferSize = inputStream.Length < BUFFER_SIZE ? inputStream.Length : BUFFER_SIZE;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
long bytesWritten = 0;
while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
{
outputStream.Write(buffer,
0, bytesRead);
bytesWritten
+= bufferSize;
}
}

虽然我对于流的理解并不是很好,但是大概的代码思路还是有的。感觉这里并没什么问题。可是,结果却并非我所想象的那样。

首先,在本地进行测试,并无错误。

然后部署到IIS中,其中包括iis7和iis6.一开始并不报错,但是后来就开始出错了。

报的最频繁的错误就是,无法访问已关闭的流。

还有一个是什么具体的忘记了。最直观的就是,当下载的压缩包并不大时,大概在20-30M左右,没事。一旦在大点,就开始报错了。

这让我很不能理解。

我在一台服务器上设置了everyone的权限,没事了,当去掉everyone的权限后,错误又出现了。

此时,开始考虑错误。

个人的想法是:压缩包从内存流中读取,然后通过文件流进行写入,此时应当会产生一个临时文件,但是这个临时文件的大小是受限的,一旦太大

就无法继续进行写入了。从而关闭文件流。

当然,到底是否是正确的,还无从得知。因为,到现在也没有获得正确的答案。

最终使用了另外的一种压缩方法进行压缩。

在此,将问题抛出,静等园内众神指点解惑。

PS:貌似直接ouputstream不行。

posted @ 2011-05-11 17:13 风疑 阅读(1335) 评论(5) 编辑 收藏

 回复 引用 查看   
#1楼2011-05-12 10:51 | 深海沉      
等答案
 回复 引用 查看   
#2楼2011-05-12 12:33 | Yong Zhang      
最直观的就是,当下载的压缩包并不大时,大概在20-30M左右,没事。一旦在大点,就开始报错了。
-> 内存引起的。如果一个用户下载 要30M,10个用户同时下载呢?

我在一台服务器上设置了everyone的权限,没事了,当去掉everyone的权限后,错误又出现了
--> 我觉得和这个应该是没有关系,如果说有关系的话,那就是每次都会下载不成功,可能是没有读取文件的权限。总之这个有问题的话,出现的问题的现象不应该是你描述的那样。

个人的想法是:压缩包从内存流中读取,然后通过文件流进行写入,此时应当会产生一个临时文件,但是这个临时文件的大小是受限的,一旦太大
就无法继续进行写入了。从而关闭文件流
-> 使用MemoryStream 肯定不会产生临时文件。

综上:
1) 仔细检查出问题的时候内存方面的情况。是一个用户引起的,还是多个用户同时操作引起的。
2)最佳的解决方案应该是边压缩,边下载。但是要注意,每次要stream.Flush, 这样就不会有问题了。你可以超这个思路去想,具体代码你自己在网上找找。








 回复 引用 查看   
#3楼[楼主]2011-05-12 16:12 | 风疑      
@Yong Zhang
首先,感谢你的回复。
--------
最直观的就是,当下载的压缩包并不大时,大概在20-30M左右,没事。一旦在大点,就开始报错了。
-> 内存引起的。如果一个用户下载 要30M,10个用户同时下载呢?

此时是单用户,还没考虑到多用户,不过估计同样会出错。

------
我在一台服务器上设置了everyone的权限,没事了,当去掉everyone的权限后,错误又出现了
--> 我觉得和这个应该是没有关系,如果说有关系的话,那就是每次都会下载不成功,可能是没有读取文件的权限。总之这个有问题的话,出现的问题的现象不应该是你描述的那样。

问题确实是存在的,没有everyone权限的时候,下载小的文件时不会出错的,当然还是单用户。不过我估计可能会跟下面的问题有关联。
-------
个人的想法是:压缩包从内存流中读取,然后通过文件流进行写入,此时应当会产生一个临时文件,但是这个临时文件的大小是受限的,一旦太大
就无法继续进行写入了。从而关闭文件流
-> 使用MemoryStream 肯定不会产生临时文件。

最终是返回的file,并不是直接使用的outputstream,而且在这个page的方法里,貌似只能是file流。
------
其实,我已经解决了,但是是用的另外一种方案,只不过我心里很纠结,想弄明白为什么这种方式会出错。
再次感谢你的回复。

 回复 引用 查看   
#4楼2011-05-12 17:05 | Yong Zhang      
@风疑
你的学习精神值得肯定!向你学习。具体到这个问题上面,你可能还自己多Debug一下,个人觉得这里面问题并不大,可以找出贵亏祸首。

“最终是返回的file,并不是直接使用的outputstream,而且在这个page的方法里,貌似只能是file流。”

public ActionResult DownLoad(int id) =>DownLoad(int id, Response.OutputStream output)//把下载页面的Response传进来
==> using (MemoryStream ms = new MemoryStream()) 就不需要了, 直接换成 output.

outputStream.Write(buffer, 0, bytesRead);
后面一定要 .Flush();
这样写入的数据就直接送到了网络上去去了。

按照我的理解,这样就不管多大的文件,多少人同时下载,应该都没有内存问题。






 回复 引用 查看   
#5楼[楼主]2011-05-16 11:16 | 风疑      
@Yong Zhang
其实,最终的解决方案和你所说的差不多,还是output管用。
ps:你说,如果在生成压缩的过程中,内存不够了咋办?