gpt4 book ai didi

php - 使用 proc_open 流运行时生成的 gzip 文件

转载 作者:行者123 更新时间:2023-12-04 08:12:26 27 4
gpt4 key购买 nike

我正在尝试流式传输 tar.gz无需在内存中缓冲任何内容或将数据保存在磁盘中。我需要 gzip 一堆 PDF 文件(每个文件约 100kb)。
如果通过脚本发送 10-20 字节的小文本文件并且用户下载可读的 tar.gz,一切似乎都正常。文件,但在发送真实数据(运行时生成的 PDF 文件)时,脚本会阻塞并停止
下面是代码的一个片段。为什么写入 stdin 时脚本会阻塞经过几次循环迭代?它在这一点上停止等待某事
在写入 stdin 之前,每个步骤都会记录到一个文件中以查看消息。是最后记录的消息

$proc = proc_open('gzip - -c', [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
], $pipes);

stream_set_read_buffer($pipes[1], 0);
stream_set_read_buffer($pipes[2], 0);

stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);

while(true){
log_step('file stream');
// fetching data from database and generating PDF file as tar stream (string)

log_step('stdin: '.strlen($tar_string));
fwrite($pipes[0], $tar_string); // <--- in the second iteration the script blocks/stops here!
log_step('stdin done!');

if($output = stream_get_contents($pipes[1])){
log_step('output: '.strlen($output));
echo $output;
}
}
输出日志文件
2021-01-26 10:28:29 file stream
2021-01-26 10:28:29 stdin: 116224
2021-01-26 10:28:29 stdin done!
2021-01-26 10:28:29 output: 32768
2021-01-26 10:28:29 file stream
2021-01-26 10:28:29 stdin: 116736
完整代码
$proc = proc_open('gzip - -c', [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
], $pipes);
stream_set_read_buffer($pipes[1], 0);
stream_set_read_buffer($pipes[2], 0);
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);

// get data from database
while($row = $result->fetch()){
// generate PDF

$filename = $pdf['name'];
$filesize = strlen($pdf['data']);

$header = pack(
'a100a8a8a8a12A12a8a1a100a255',
$filename,
sprintf('%6s ', ''),
sprintf('%6s ', ''),
sprintf('%6s ', ''),
sprintf('%11s ', $filesize),
sprintf('%11s', ''),
sprintf('%8s ', ' '),
0,
'',
''
);

$checksum = 0;
for($i=0; $i<512; $i++){
$checksum += ord($header{$i});
}

$checksum_data = pack(
'a8',
sprintf('%6s ', decoct($checksum))
);

for($i=0, $j=148; $i<8; $i++, $j++){
$header{$j} = $checksum_data{$i};
}

fwrite($pipes[0], $header.$pdf['data'].pack(
'a'.(512 * ceil($filesize / 512) - $filesize),
''
));

if($output = stream_get_contents($pipes[1])){
echo $output;
}
}

fwrite($pipes[0], pack('a512', ''));
fclose($pipes[0]);

while(true){
if($output = stream_get_contents($pipes[1])){
echo $output;
}

if(!proc_get_status($proc)['running']){
foreach($pipes as $pipe){
if(is_resource($pipe)){
fclose($pipe);
}
}
proc_close($proc);

break;
}
}

最佳答案

你的脚本没有进展的原因是它试图向管道写入比 gzip 更多的数据。进程能够立即处理。情况大致如下:

  • 您的脚本将 116736 字节写入管道。
  • gzip进程从其标准输入读取其中的一些数据,对其进行压缩,并在其标准输出上输出压缩数据。
  • PHP进程被阻塞,直到gzip进程读取它写入管道的其余输入。
  • gzip进程被阻塞,直到 PHP 进程读取它写入标准输出的压缩输出。

  • 所以你的脚本发现自己陷入了僵局。
    问题的根源在于,与 C 中的同名不同,PHP fwrite阻塞模式下的函数将始终尝试将整个缓冲区写入流,直到写入所有内容。这可以通过在标准输入管道上启用非阻塞模式来解决,并监控实际写入了多少输入。例如像这样:
    $proc = proc_open('gzip -c -', [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    ], $pipes);

    stream_set_read_buffer($pipes[1], 0);

    stream_set_blocking($pipes[0], false);
    stream_set_blocking($pipes[1], false);

    $tar_string = '';
    for (;;) {
    if ($tar_string === '') {
    if (/* more input available */)
    $tar_string = /* read more input */;
    else {
    $tar_string = null;
    \fclose($pipes[0]);
    }
    }

    if ($tar_string !== null) {
    $written = \fwrite($pipes[0], $tar_string);
    if ($written === false)
    throw new \Exception('write error');
    $tar_string = \substr($tar_string, $written);
    }

    /* THIS IS JUST SOME DUMB DEMONSTRATIVE CODE, DO NOT COPY-PASTE */

    for (;;) {
    $outbuf = \fread($pipes[1], 69420);
    if ($outbuf === false)
    throw new \Exception('read error');
    if ($outbuf === '')
    break;
    $outlen = \strlen($outbuf);
    echo $outbuf;
    }

    if (\feof($pipes[1]))
    break;
    }
    以上将 表面上工作。一个很大的缺点是它的表现会非常糟糕:当 gzip进程未准备好读取或写入任何数据,脚本将无用地保持忙循环并从 gzip 中占用 CPU 时间。实际需要它的过程。
    在更明智的编程语言中,您可以访问:
  • 电话如 poll select ,它能够在流准备好被读取或写入时发出信号,否则将 CPU 时间交给可能需要它的其他进程;
  • 可以在成功部分读取或写入后立即返回的 I/O 原语,而不是尝试处理缓冲区的整个大小。

  • 但这是 PHP,所以我们不能有好东西。至少不是内置的。
    然而,对于这个问题,有一个更好的解决方案可以避免 proc_open完全,而是使用 zlib 扩展实现 gzip 压缩,如下所示:
    $zctx = \deflate_init(ZLIB_ENCODING_GZIP);
    if ($zctx === false)
    throw new \Exception('deflate_init failed');

    while (/* more data available */) {
    $input = /* get more data */;
    $data = \deflate_add($zctx, $input, ZLIB_NO_FLUSH);
    if ($data === false)
    throw new \Exception('deflate_add failed');
    echo $data;
    }

    $data = \deflate_add($zctx, '', ZLIB_FINISH);
    if ($data === false)
    throw new \Exception('deflate_add failed');
    echo $data;

    unset($zctx); // free compressor resources
    deflate_init deflate_add 自 PHP 7 起可用,假设在构建 PHP 时启用了 zlib 扩展。调用库比调用子进程(实际上是任何语言)更可取,因为它更轻量级:将所有内容放在同一个进程中可以避免内存和上下文切换开销。

    关于php - 使用 proc_open 流运行时生成的 gzip 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65883806/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com