Использование фильтра zlib с парой сокетов - PullRequest
6 голосов
/ 22 сентября 2011

По какой-то причине фильтр 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, о которой я должен сообщить, или ошибка с моей стороны?

Этот вопрос является ответвлением от решения этого связанного вопроса .

Ответы [ 3 ]

3 голосов
/ 02 ноября 2012

Я работал над исходным кодом 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 * case), расширяющем

if (flags & PSFS_FLAG_FLUSH_CLOSE) {

управление FLUSH_INC тоже:

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

Этот загружаемый патч предназначен для debian squeeze версии PHP, но текущая git-версия файла ближе к нему, поэтому я полагаю, что порт исправленияпросто (несколько строк).

Если возникнет побочный эффект, пожалуйста, свяжитесь со мной.

2 голосов
/ 28 сентября 2011

При просмотре исходного кода C проблема заключается в том, что фильтр всегда позволяет функции zlib deflate() решать, сколько данных накапливать, прежде чем создавать сжатый вывод. Фильтр дефляции не создает новый блок данных для передачи, если только deflate() не выводит некоторые данные (см. Строку 235) или не установлен бит флага PSFS_FLAG_FLUSH_CLOSE (строка 250). Вот почему вы видите только байты заголовка, пока не закроете $in; первый вызов deflate() выводит два байта заголовка, поэтому data->strm.avail_out равен 2, и создается новый сегмент для передачи этих двух байтов.

Обратите внимание, что fflush() не работает из-за известной проблемы с фильтром zlib. См .: Ошибка # 48725 Поддержка очистки в потоке zlib .

К сожалению, здесь, похоже, нет хорошего обходного пути. Я начал писать фильтр в PHP, расширив php_user_filter, но быстро столкнулся с проблемой, заключающейся в том, что php_user_filter не выставляет биты флага, только если flags & PSFS_FLAG_FLUSH_CLOSE (четвертый параметр метода filter(), обычно булев аргумент по имени $closing). Вам нужно будет изменить исходники C самостоятельно, чтобы исправить ошибку # 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. Можно ли одновременно писать и промывать? В любом случае обработка флагов должна выполняться вне цикла while через бригаду ведра «in», например, как обрабатывается PSFS_FLAG_FLUSH_CLOSE вне этого цикла.
  • Строка 221, от memcpy до data->strm.next_in, похоже, игнорирует тот факт, что data->strm.avail_in может быть ненулевым, поэтому сжатый вывод может пропустить некоторые данные записи. См., Например, следующий текст из руководства zlib:

    Если не все входные данные могут быть обработаны (поскольку в выходном буфере недостаточно места), next_in и avail_in обновляются, и обработка возобновляется в этот момент для следующего вызова deflate().

    Другими словами, возможно, что avail_in не равен нулю.

  • Оператор if в строке 235, if (data->strm.avail_out < data->outbuf_len), вероятно, должен быть if (data->strm.avail_out) или, возможно, if (data->strm.avail_out > 2).
  • Я не уверен, почему *bytes_consumed = consumed; не *bytes_consumed += consumed;. Примеры потоков в http://www.php.net/manual/en/function.stream-filter-register.php все используют += для обновления $consumed.

РЕДАКТИРОВАТЬ: *bytes_consumed = consumed; правильно. Стандартные реализации фильтра все используют = вместо += для обновления значения size_t, на которое указывает пятый параметр. Кроме того, даже если $consumed += ... на стороне PHP эффективно преобразуется в += на size_t (см. Строки 206 и 231 ext/standard/user_filters.c), встроенная функция фильтра вызывается либо с помощью NULL указатель или указатель на size_t, установленный в 0 для пятого аргумента (см. Строки 361 и 452 main/streams/filter.c).

1 голос
/ 28 сентября 2011

Вам нужно закрыть поток после записи, чтобы очистить его до того, как данные поступят из чтения.

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 смутила меня.

...