Web服务通常需要压缩几个大文件,以供客户端下载。最明显的方法是创建一个临时zip文件,然后将echo
其发送给用户或将其保存到磁盘并重定向(在将来的某个时间将其删除)。
但是,以这种方式进行操作有缺点:
诸如ZipStream-PHP之类的解决方案通过将数据逐个文件推送到Apache来对此进行改进。但是,结果仍然是较高的内存使用率(文件完全加载到内存中),以及磁盘和CPU使用率的大幅波动。
相比之下,请考虑以下bash代码段:
ls -1 | zip -@ - | cat > file.zip
# Note -@ is not supported on MacOS
在此,zip
以流模式运行,从而减少了内存占用。管道具有集成缓冲区–当缓冲区已满时,操作系统将挂起编写程序(位于管道左侧的程序)。这可以确保zip
工作仅与可以写入的速度一样快cat
。
然后,最佳方法是执行相同操作:替换cat
为Web服务器进程,然后将zip文件与动态创建的 流传
输给用户。与仅流式传输文件相比,这将产生很少的开销,并且具有无问题的,非尖峰的资源配置文件。
如何在LAMP堆栈上实现此目标?
您可以使用popen()
(docs)或proc_open()
(docs)执行Unix命令(例如zip或gzip),并以php流的形式返回stdout。 flush()
(docs)会尽力将php输出缓冲区的内容推送到浏览器。
将所有这些结合起来将为您提供所需的内容(前提是没有其他障碍物-尤其是在docs页面上的警告flush()
)。
( 注意 :请勿使用flush()
。有关详细信息,请参见下面的更新。)
如下所示的方法可以解决问题:
<?php
// make sure to send all headers first
// Content-Type is the most important one (probably)
//
header('Content-Type: application/x-gzip');
// use popen to execute a unix command pipeline
// and grab the stdout as a php stream
// (you can use proc_open instead if you need to
// control the input of the pipeline too)
//
$fp = popen('tar cf - file1 file2 file3 | gzip -c', 'r');
// pick a bufsize that makes you happy (64k may be a bit too big).
$bufsize = 65535;
$buff = '';
while( !feof($fp) ) {
$buff = fread($fp, $bufsize);
echo $buff;
}
pclose($fp);
您询问了“其他技术”:我将对它说,“在请求的整个生命周期中支持无阻塞I / O的任何东西”。 如果 您愿意进入非阻塞文件访问的“麻烦”阶段, 则
可以使用Java或C / C ++(或许多其他可用语言)将这样的组件构建为独立服务器。
如果您想要一个非阻塞的实现,但是您宁愿避免“崩溃”,最简单的路径(IMHO)将是使用nodeJS。现有的nodejs版本中需要的所有功能都有大量支持:(http
当然)将模块用于http服务器;并使用child_process
模块生成tar
/ zip /任何管道。
最后,如果(且仅当)您正在运行多处理器(或多核)服务器,并且希望从nodejs获得最大收益,则可以使用Spark2在同一端口上运行多个实例。每个处理器核心不要运行多个nodejs实例。
更新 (来自Benji关于此答案的评论部分中的出色反馈)
1. docs fread()
表示该功能一次只能从非常规文件中读取最多8192字节的数据。因此,8192可能是缓冲区大小的不错选择。
[版本说明] 8192几乎可以肯定是一个与平台有关的值-
在大多数平台上,fread()
它将读取数据,直到操作系统的内部缓冲区为空,此时它将返回,从而允许os再次异步填充该缓冲区。8192是许多流行的操作系统上默认缓冲区的大小。
在其他情况下,可能会导致fread返回少于8192字节的内容-例如,“远程”客户端(或进程)填充缓冲区的速度很慢-
在大多数情况下,fread()
输入缓冲区的内容将返回-无需等待它充满。这可能意味着返回0..os_buffer_size个字节。
道德是:传递给fread()
as 的值buffsize
应被视为“最大”大小-永远不要假设您已收到要求的字节数(或与此有关的任何其他数字)。
2.
根据对fread文档的评论,有几点警告:魔术引号可能会干扰并且必须将其关闭。
3. 设置mb_http_output('pass')
(文档)可能是一个好主意。虽然'pass'
已经是默认设置,但是如果您的代码或配置先前已将其更改为其他设置,则可能需要显式指定它。
4. 如果要创建一个zip文件(而不是gzip文件),则要使用内容类型标头:
Content-type: application/zip
或…可以改用’application / octet-stream’。(这是用于各种二进制下载的通用内容类型):
Content-type: application/octet-stream
并且如果您希望提示用户下载文件并将其保存到磁盘(而不是让浏览器尝试将文件显示为文本),则需要content-
disposition标头。(其中filename表示应在保存对话框中建议的名称):
Content-disposition: attachment; filename="file.zip"
还应该发送Content-length标头,但是用这种技术很难做到这一点,因为您事先不知道zip的确切大小。
是否可以设置标题以指示内容正在“流式传输”或长度未知? 有人知道吗
最后,这是一个修改后的示例,它使用了@ Benji的所有建议(并创建了一个ZIP文件而不是TAR.GZIP文件):
<?php
// make sure to send all headers first
// Content-Type is the most important one (probably)
//
header('Content-Type: application/octet-stream');
header('Content-disposition: attachment; filename="file.zip"');
// use popen to execute a unix command pipeline
// and grab the stdout as a php stream
// (you can use proc_open instead if you need to
// control the input of the pipeline too)
//
$fp = popen('zip -r - file1 file2 file3', 'r');
// pick a bufsize that makes you happy (8192 has been suggested).
$bufsize = 8192;
$buff = '';
while( !feof($fp) ) {
$buff = fread($fp, $bufsize);
echo $buff;
}
pclose($fp);
更新
:(2012-11-23)我发现flush()
在处理非常大的文件和/或非常慢的网络时,在读/回显循环中调用会导致问题。至少,当在Apache后面以cgi
/
fastcgi的身份运行PHP时,这是正确的,并且当在其他配置中运行时,似乎也可能发生相同的问题。当PHP将输出刷新到Apache的速度比Apache实际通过套接字发送输出的速度快时,就会出现此问题。对于非常大的文件(或连接速度慢),最终会导致Apache内部输出缓冲区溢出。这会导致Apache终止PHP进程,这当然会导致下载挂起或过早完成,而只进行了部分传输。
解决的办法是 根本不 打电话flush()
。我已经更新了上面的代码示例以反映这一点,并在答案顶部的文本中添加了一个注释。
注意:我更喜欢使用。NET framework库(而不是外部库)来完成此操作
我正在Windows服务器上使用C#处理存储在IIS服务器上的web应用程序。 null
我试图在ZIP文件内创建一个ZIP文件,以重新构建以前在内存中的zip结构,我在Java。 我失败了,因为我得到了一个错误的内部ZIP内创建的初始ZIP文件。文件已损坏。当试图打开它时,我得到一个“文件的意外结局”。 我得到了这个结构: -input.zip--InnerInput.zip 代码使用java Stack和Map在内存中解压。然后它创建input2.zip,内部nput.zip。 总
问题内容: 我正在尝试将 文件 从 一个文件夹 保存到 另一个 文件夹 。 zip文件夹 放置在其他目录中。而且我编写了以下代码: archive.php 但是可惜我无法创建.zip文件夹。我错过了任何一步吗? 问题答案: TEST是您的项目文件夹名称。 您可以根据需要定义路径。
问题内容: 我有一个动态文本文件,可以根据用户的查询从数据库中选择内容。我必须将此内容写入文本文件,并将其压缩在servlet的文件夹中。我应该怎么做? 问题答案: 看这个例子: 这将在D:named 的根目录中创建一个文件,其中将包含一个名为的单个文件。当然,你可以添加更多的zip条目,还可以指定一个子目录,如下所示:
本文向大家介绍基本磁盘和动态磁盘之间的区别,包括了基本磁盘和动态磁盘之间的区别的使用技巧和注意事项,需要的朋友参考一下 基本磁盘和动态磁盘都是Windows操作系统中可用的磁盘配置。基本磁盘是从DOS,Windows最初的日子到现在。从Windows 2000开始可以使用动态磁盘。 基本磁盘 基本磁盘配置适用于分区,分区表和逻辑驱动器的概念。一个磁盘最多可以具有四个分区或三个分区以及一个具有多个逻