2011-09-22 8 views
6

z jakiegoś powodu, filtr zlib.deflate nie wydaje się pracować z parami gniazd generowanych przez stream_socket_pair(). Wszystko, co można odczytać z drugiego gniazda, to dwubajtowy nagłówek zlib, a wszystko po nim ma wartość NULL.Korzystanie filtr zlib z parą gniazd

Przykład:

<?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); 

wyjścia:

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

Jeśli komentarz na wezwanie do stream_filter_append(), pisanie strumień/czytania funkcje prawidłowo, z danymi dumpingu w całości wszystkie trzy razy , a jeśli kieruję strumień filtrowany zlib do pliku zamiast przez parę gniazd, skompresowane dane są zapisywane poprawnie. Więc obie części działają poprawnie osobno, ale nie razem. Czy jest to błąd PHP, który powinienem zgłosić, czy błąd z mojej strony?

to pytanie jest odgałęziony od rozwiązania this related question.

Odpowiedz

2

Patrząc przez the C source code, problem polega na tym, że filtr zawsze pozwala zlib's deflate() function decydować, ile danych zgromadzić przed wyprodukowaniem skompresowanego wyjścia. Filtr deflacji nie tworzy nowego segmentu danych do przekazania, chyba że deflate() wyświetli dane (linia 235) lub bit flag PSFS_FLAG_FLUSH_CLOSE (linia 250). Dlatego widzisz tylko bajty nagłówka, dopóki nie zamkniesz $in; pierwsze wywołanie deflate() wysyła dwa bajty nagłówka, więc data->strm.avail_out ma wartość 2, a dla tych dwóch bajtów jest tworzone nowe wiadro.

Zauważ, że fflush() nie działa ze względu na znany problem z filtrem zlib. Patrz: Bug #48725 Support for flushing in zlib stream.

Niestety, nie wydaje się, żeby to było miłe. Zacząłem pisać filtry w PHP przez rozszerzenie php_user_filter, ale szybko wpadłem na problem polegający na tym, że php_user_filter nie eksponuje bitów flag, tylko czy flags & PSFS_FLAG_FLUSH_CLOSE (czwarty parametr metody filter(), boolowski argument zwykle o nazwie $closing). Będziesz musiał sam zmodyfikować źródła C, aby poprawić błąd # 48725. Alternatywnie, przepisz go ponownie.

Osobiście uważam ponowne pisanie, bo wydaje się, że kilka kwestii podnoszenia brwi z kodem:

  • status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH)); wydaje się dziwne, bo pisząc, nie wiem dlaczego flags byłoby coś inne niż PSFS_FLAG_NORMAL. Czy możliwe jest jednoczesne pisanie &?W każdym przypadku obsługa flag powinna odbywać się poza pętlą while poprzez bryłę "wiadra" w "in", tak jak w przypadku tej operacji jest przetwarzana PSFS_FLAG_FLUSH_CLOSE.
  • Linia 221, memcpy do wydaje się ignorować fakt, że data->strm.avail_in może być niezerowa, więc skompresowane dane wyjściowe mogą pomijać niektóre dane zapisu. Patrz, na przykład, poniższy tekst z podręcznika zlib:

    Jeśli nie wszystkie wejścia mogą być przetwarzane (ponieważ nie ma wystarczająco dużo miejsca w buforze wyjściowym), next_in i avail_in są aktualizowane i przetwarzanie wznowi w tym punkt dla następnego połączenia deflate().

    Innymi słowy, możliwe jest, że avail_in jest niezerowe.

  • Polecenie if na linii 235, if (data->strm.avail_out < data->outbuf_len) powinno być prawdopodobnie if (data->strm.avail_out) lub być może if (data->strm.avail_out > 2).
  • Nie jestem pewien, dlaczego *bytes_consumed = consumed; nie jest . Przykładowe strumienie o numerach http://www.php.net/manual/en/function.stream-filter-register.php wszystkie używają += do aktualizacji $consumed.

EDIT:*bytes_consumed = consumed; jest poprawna. The standard filter implementations wszystkie używają = zamiast += do aktualizacji wartości size_t wskazanej przez piąty parametr. Ponadto, mimo że $consumed += ... po stronie PHP skutecznie przekłada się na += na size_t (patrz linie 206 i 231 z ext/standard/user_filters.c), natywna funkcja filtru jest wywoływana za pomocą wskaźnika NULL lub wskaźnika do size_t ustawionego na 0 dla piątego argumentu (patrz linie 361 i 452 z main/streams/filter.c).

+0

Dziękuję bardzo za wyjaśnienie. Zaimplementowałem ten sam projekt w Ruby i ostatecznie musiałem przekazać 'Zlib :: SYNC_FLUSH' jako drugi argument do' Zlib :: deflate() ', aby działało. Zakładam, że to jest pisanie, a następnie natychmiastowe spłukiwanie po napisaniu. Zauważam, że PHP używa tylko 'Z_SYNC_FLUSH', jeśli ustawiona jest flaga' PSFS_FLAG_FLUSH_INC', ale jak powiedziałeś, bity flag nie wydają się być odsłonięte. – FtDRbwLXw6

1

Musisz zamknąć strumień po zapisie, aby go opróżnić, zanim dane wejdą z odczytu.

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"; 

która produkuje: Sprężone: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd70300532b079c Sprężone: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd7512849cc4e552829cfd70300b1b50b07 Sprężone: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29ca0452ba0a25199945290a259940c9cc62202f55213923b128d71e008e4c108c

Również przestawił $ i $, bo w piśmie do $ mylić mnie.

+0

Dziękuję za odpowiedź, ale to rozwiązanie nie jest wykonalne.Narzut otwierania/zamykania gniazd po każdym zapisie byłby samowystarczalny, ale to także niszczy filtr zlib po każdym zapisie, co przerwałoby implementację. Punkt użycia filtru zlib jest taki, że kolejne zapisy używają tego samego filtru. Z pewnością musi być sposób na spłukiwanie bez zamykania? Próbowałem wcześniej 'fflush()', bez powodzenia. ** Edycja: ** Aby być bardziej zrozumiałym, wysłanie nagłówka przy każdym zapisie przerwałoby implementację, ponieważ ma być wysłane tylko raz. – FtDRbwLXw6

3

Pracowałem nad kodem źródłowym PHP i znalazłem poprawkę.

Aby zrozumieć, co się dzieje, ja prześledzić kod podczas

.... 
for ($i = 0 ; $i < 3 ; $i++) { 
    fwrite($s[0], ...); 
    fread($s[1], ...); 
    fflush($s[0], ...); 
    fread($s[1], ...); 
    } 

pętli i znalazłem, że funkcja deflate nigdy nie jest wywoływana z zestawu Z_SYNC_FLUSH flagi ponieważ żadne nowe dane są obecne w backets_in brygady.

Moja poprawka jest zarządzanie (PSFS_FLAG_FLUSH_INC flaga jest ustawiona AND żadnych iteracje są wykonywane na funkcję DEFLATE przypadek) przedłużające zarządzanie FLUSH_INC

if (flags & PSFS_FLAG_FLUSH_CLOSE) { 

TOO:

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

This downloadable patch jest dla debian squeeze wersja PHP ale aktualna wersja pliku git jest bliżej niego, więc przypuszczam, że poprawka jest po prostu prosta (kilka linii).

Jeśli pojawi się jakikolwiek efekt uboczny, skontaktuj się ze mną.