【问题标题】:Using zlib filter with a socket pair使用带有套接字对的 zlib 过滤器
【发布时间】:2011-11-22 10:51:34
【问题描述】:

由于某种原因,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的解决方案分支出来的。

【问题讨论】:

    标签: php sockets stream zlib


    【解决方案1】:

    我曾研究过 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 用于debian squeeze 版本的 PHP,但文件的当前 git 版本更接近它,​​所以我想移植修复很简单(几行)。

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

    【讨论】:

      【解决方案2】:

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

      请注意,fflush() 不起作用,因为 zlib 过滤器存在已知问题。见:Bug #48725 Support for flushing in zlib stream

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

      我个人会考虑重写它,因为代码似乎有一些令人眼花缭乱的问题:

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

        如果不能处理所有输入(因为输出缓冲区中没有足够的空间),next_inavail_in 将被更新,并且处理将在此时恢复处理,以便下一次调用 deflate()

        换句话说,avail_in 可能不为零。

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

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

      【讨论】:

      • 非常感谢您的解释。我在 Ruby 中实现了相同的项目,最终不得不将 Zlib::SYNC_FLUSH 作为第二个参数传递给 Zlib::deflate() 以使其工作。我假设这是在写入,然后在写入后立即刷新。我注意到如果设置了PSFS_FLAG_FLUSH_INC 标志,PHP 仅使用Z_SYNC_FLUSH,但正如您所说,标志位似乎没有被暴露。
      【解决方案3】:

      您需要在写入后关闭流以刷新它,然后数据才能从读取中进入。

      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 和 $out,因为写到 $in 让我很困惑。

      【讨论】:

      • 谢谢您的回复,但是这个方案并不可行。每次写入后打开/关闭套接字的开销本身是令人望而却步的,但这也会在每次写入后破坏 zlib 过滤器,这会破坏实现。使用 zlib 过滤器的目的是让连续写入使用相同的过滤器。当然必须有一种无需关闭即可冲洗的方法吗?我之前尝试过fflush(),但运气不佳。 编辑: 更清楚地说,每次写入都发送标头会破坏实现,因为它只打算发送一次。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-25
      • 1970-01-01
      • 1970-01-01
      • 2022-09-29
      • 2020-10-28
      • 2011-06-04
      相关资源
      最近更新 更多