PHP fwrite()文件导致内部服务器错误

时间:2014-09-23 16:36:03

标签: php file memory fwrite

我想将大文件上传到我的服务器。我在上传之前拼接这些文件。 1MB块。如果上传了一个块,则该块将附加到该文件。 在我的本地服务器上一切正常,但如果我在我的Web服务器上测试此脚本(由Strato -.-托管),则每次服务器上的附加文件大到64MB时,进程都会退出内部服务器错误。 我认为它(当然?)是由于Strato的限制造成的。也许有记忆的东西,但我无法向自己解释为什么会发生这种情况。 这是脚本(PHP 5.6版):

$file = $_FILES['chunk'];
$server_chunk = fopen($uploadDir.$_POST['file_id'], "ab");
$new_chunk = fopen($file['tmp_name'], "rb");

while(!feof($new_chunk)) //while-loop is optional
{
     fwrite($server_chunk, fread($new_chunk, 1024));
};
fclose($new_chunk);
fclose($server_chunk);

在我看来,这段代码中没有一行将文件加载到内存中导致此错误,或者是否有不同的行导致此错误?

我检查了服务器日志,但是如果发生此错误则没有条目。

php.ini

我可以创建多个63MB文件。只有当文件超过64MB时,服务器才会中止。

更新 我编写了以下脚本来连接服务器上的filechunks和cat。 但我总是得到一个8192B文件。 这个脚本有问题吗? $ command类似于:
/ bin / cat ../files/8_0 ../files/8_1 ../files/8_2 ../ files / 8_3

$command = '/bin/cat';
foreach($file_array AS $file_info)
{
    $command = $command.' ../files/'.$file_info['file_id'].'_'.$file_info['server_chunkNumber'];
}
$handle1 = popen($command, "r");
$read = fread($handle1, $_GET['size']);
echo $read;

我检查了结果。 8192B文件中的字节与原始文件的开头完全相同。所以似乎有些东西......

更新: 我找到了this

更新

$handle1 = popen($command, "r");
while(!feof($handle1))  
{ 
    $read = fread($handle1, 1024);
    echo $read;
}

这样可行,我可以从句柄中逐段读取。但当然这种方式我会遇到超时限制。我怎样才能将文件传递给客户端?如果这个问题得到解答,我的所有问题都消失了;)

1 个答案:

答案 0 :(得分:2)

(请参阅底部的更新)

内存限制错误看起来会有所不同(但您可以编写一个脚本,通过向不断增长的数组添加大对象来连续分配内存,并查看会发生什么)。此外,内存限制与PHP核心,脚本,结构,以及任何文件内容有关;即使附加到的文件被加载或计入内存限制(可能通过mmap,即使它看起来很奇怪),也不太可能最多限制为64兆字节。

所以这看起来像对单个文件大小的文件系统限制。有几个云文件系统有这样的限制;但我知道在本地磁盘存储上没有这样的。我要求提供托管技术支持的线索。

我做了一些尝试:

  • 仔细检查$uploadDir,除非是您亲自指定的。

  • 尝试使用与$uploadDir不同的路径创建文件,除非肯定在同一个文件系统上

  • 尝试检查错误:

    if (!fwrite($server_chunk, fread($new_chunk, 1024))) {
       die("Error writing: ");
    }
    
  • 对phpinfo()进行偏执检查以确保没有一些真正奇怪的奇怪,例如function overriding。您可以enumerating the functions进行调查并查看(可欺骗,是的,但不太可能)。

更新

它看起来像文件大小限制,与PHP无关。同一主机的早期用户报告了32 Mb。请参阅herehere。那些人无法运行mysqldumptar备份。它与PHP无直接关系。

解决方法......似乎有效!

您可以解决此问题,方法是将文件存储在块中,然后分几个部分下载,或者将Apache管道传递给cat程序(如果可用)。

您将存储file123.0001,file123.0002,...,然后在下载时检查所有片段,发送相应的Content-Length,为/bin/cat构建命令行(如果可访问... )并将流连接到服务器。你可能仍然遇到时间限制,但值得一试。

示例:

<?php
    $pattern    = "test/zot.*"; # BEWARE OF SHELL METACHARACTERS
    $files      = glob($pattern);
    natsort($files);
    $size       = array_sum(array_map('filesize', $files));
    ob_end_clean();
    Header("Content-Disposition: attachment;filename=\"test.bin\";");
    Header("Content-Type: application/octet-stream");
    Header("Content-Length: {$size}");
    passthru("/bin/cat {$pattern}");

我测试了上面的内容,它从一堆10-Mb块中下载了一个120Mb文件,命令如zot.1,zot.2,...,zot.10,zot.11,zot.12( 是的,我最初没有使用natsort )。如果我能找到时间,我将在具有限制网络的VM中再次运行它,这样脚本有10秒的时间限制,下载需要20秒。 可能 PHP 终止脚本直到passthru返回,因为我发现PHP的计时不是很直观。

以下代码的运行时间限制为三秒。它运行一个 4 秒的命令,然后将输出发送到浏览器,一直运行直到时间耗尽。

<pre>
<?php
    print "It is now " . date("H:i:s") . "\n";
    passthru("sleep 4; echo 'Qapla!'");
    print "It is now " . date("H:i:s") . "\n";
    $x = 0;
    for ($i = 0; $i < 5; $i++) {
        $t = microtime(true);
        while (microtime(true) < $t + 1.0) {
            $x++;
        }
        echo "OK so far. It is now " . date("H:i:s") . "\n";
    }

结果是:

It is now 20:52:56
Qapla!
It is now 20:53:00
OK so far. It is now 20:53:01
OK so far. It is now 20:53:02
OK so far. It is now 20:53:03


( ! ) Fatal error: Maximum execution time of 3 seconds exceeded in /srv/www/rumenta/htdocs/test.php on line 9

Call Stack
#   Time    Memory  Function    Location
1   0.0002  235384  {main}( )   ../test.php:0
2   7.0191  236152  microtime ( )   ../test.php:9

当然,Strato可能会对脚本的运行时间进行更强有力的检查。另外,我安装了PHP作为模块;可能对于作为独立流程运行的CGI,适用不同的规则。