使用带套接字对的zlib过滤器

时间:2011-09-22 02:01:08

标签: php sockets stream zlib

由于某种原因,zlib.deflate过滤器似乎不适用于stream_socket_pair()生成的套接字对。所有可以从第二个套接字读取的是双字节zlib头,之后的所有内容都是NULL。

示例:

<?php
list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
                                     STREAM_SOCK_STREAM,
                                     STREAM_IPPROTO_IP);

$params = array('level' => 6, 'window' => 15, 'memory' => 9);

stream_filter_append($in, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($in, 0);
stream_set_blocking($out, 0);

fwrite($in, 'Some big long string.');
$compressed = fread($out, 1024);
var_dump($compressed);

fwrite($in, 'Some big long string, take two.');
$compressed = fread($out, 1024);
var_dump($compressed);

fwrite($in, 'Some big long string - third time is the charm?');
$compressed = fread($out, 1024);
var_dump($compressed);

输出:

string(2) "x�"
string(0) ""
string(0) ""

如果我注释掉stream_filter_append()的调用,则流写入/读取功能正常,数据全部被转储三次,如果我将zlib过滤后的流导入文件而不是通过套接字对,压缩数据写得正确。因此两个部分分别正确运行,但不能一起运行。这是我应该报告的PHP错误,还是我的错误?

此问题从解决方案分支到this related question

3 个答案:

答案 0 :(得分:3)

我曾参与PHP源代码并找到了解决方法。

要了解在

期间我跟踪代码的情况
....
for ($i = 0 ; $i < 3 ; $i++) {
    fwrite($s[0], ...);
    fread($s[1], ...);
    fflush($s[0], ...);
    fread($s[1], ...);
    }

循环,我发现deflate函数永远不会被设置为Z_SYNC_FLUSH标志,因为backets_in旅中没有新数据。

我的修复是管理( PSFS_FLAG_FLUSH_INC标志设置AND没有对deflate函数执行迭代案例)扩展

if (flags & PSFS_FLAG_FLUSH_CLOSE) {

管理FLUSH_INC

if (flags & PSFS_FLAG_FLUSH_CLOSE || (flags & PSFS_FLAG_FLUSH_INC && to_be_flushed)) {

This downloadable patch适用于PHP的debian squeeze版本,但文件的当前git版本更接近它,​​所以我想将端口修复只是(几行)。

如果出现一些副作用,请与我联系。

答案 1 :(得分:2)

翻阅the C source code,问题是过滤器总是让zlib's deflate() function决定在生成压缩输出之前要累积多少数据。除非deflate()输出一些数据(参见第235行)或设置PSFS_FLAG_FLUSH_CLOSE标志位(第250行),否则deflate过滤器不会创建要传递的新数据存储桶。这就是为什么在关闭$in之前只看到头字节的原因;第一次调用deflate()会输出两个标头字节,因此data->strm.avail_out为2,并为这两个字节创建一个新的存储桶来传递。

请注意fflush()因zlib过滤器的已知问题而无效。请参阅:Bug #48725 Support for flushing in zlib stream

不幸的是,似乎没有一个很好的解决方法。我开始通过扩展php_user_filter在PHP中编写过滤器,但很快就遇到了php_user_filter不暴露标志位的问题,只是flags & PSFS_FLAG_FLUSH_CLOSEfilter()的第四个参数方法,一个通常名为$closing的布尔参数。您需要自己修改C源代码以修复Bug#48725。或者,重写它。

就我个人而言,我会考虑重新编写它,因为代码似乎有一些令人费解的问题:

  • status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH));似乎很奇怪,因为写作时,我不知道为什么flags不是PSFS_FLAG_NORMAL。是否可以写&amp;同时冲洗?在任何情况下,处理标志都应该在while循环之外通过“in”bucket旅进行,就像在此循环之外处理PSFS_FLAG_FLUSH_CLOSE一样。
  • 第221行,memcpydata->strm.next_in似乎忽略了data->strm.avail_in可能不为零的事实,因此压缩输出可能会跳过某些写入数据。例如,请参阅zlib手册中的以下文本:

      

    如果不能处理所有输入(因为输出缓冲区中没有足够的空间),next_inavail_in会更新,此时将继续处理,以便下一次调用{{ 1}}。

    换句话说,deflate()可能不为零。

  • 第235行avail_in语句,if可能应为if (data->strm.avail_out < data->outbuf_len)if (data->strm.avail_out)
  • 我不确定为什么if (data->strm.avail_out > 2)不是*bytes_consumed = consumed;http://www.php.net/manual/en/function.stream-filter-register.php的示例流都使用*bytes_consumed += consumed;来更新+=

编辑: $consumed是正确的。 The standard filter implementations全部使用*bytes_consumed = consumed;而不是=来更新第五个参数指向的+=值。此外,即使PHP端的size_t$consumed += ...上有效转换为+=(参见ext/standard/user_filters.c的第206和231行),也会调用本机过滤器函数对于第五个参数,size_t指针或指向NULL的指针设置为0(参见main/streams/filter.c的第361行和第452行)。

答案 2 :(得分:1)

您需要在写入之后关闭流以在数据从读取进入之前对其进行刷新。

list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
                                     STREAM_SOCK_STREAM,
                                     STREAM_IPPROTO_IP);

$params = array('level' => 6, 'window' => 15, 'memory' => 9);

stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);

fwrite($out, 'Some big long string.');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";


list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
                                     STREAM_SOCK_STREAM,
                                     STREAM_IPPROTO_IP);

$params = array('level' => 6, 'window' => 15, 'memory' => 9);

stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);


fwrite($out, 'Some big long string, take two.');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";

list($in, $out) = stream_socket_pair(STREAM_PF_UNIX,
                                     STREAM_SOCK_STREAM,
                                     STREAM_IPPROTO_IP);

$params = array('level' => 6, 'window' => 15, 'memory' => 9);

stream_filter_append($out, 'zlib.deflate', STREAM_FILTER_WRITE, $params);
stream_set_blocking($out, 0);
stream_set_blocking($in, 0);

fwrite($out, 'Some big long string - third time is the charm?');
fclose($out);
$compressed = fread($in, 1024);
echo "Compressed:" . bin2hex($compressed) . "<br>\n";

产生: 压缩:789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd70300532b079c 压缩:789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd7512849cc4e552829cfd70300b1b50b07 压缩:789c0bcecf4d5548ca4c57c8c9cf4b57282e29ca0452ba0a25199945290a259940c9cc62202f55213923b128d71e008e4c108c

此外,我将$ in和$切换,因为写入$让我很困惑。