2011-09-22 34 views
6

Per qualche motivo, il filtro zlib.deflate non sembra funzionare con le coppie di socket generate da stream_socket_pair(). Tutto ciò che può essere letto dal secondo socket è l'intestazione zlib a due byte, e tutto ciò che segue è NULL.Utilizzo del filtro zlib con una coppia di socket

Esempio:

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

uscita:

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

Se io commento la chiamata a stream_filter_append(), la scrittura flusso/lettura funzioni correttamente, con i dati oggetto di dumping nella sua interezza tutte e tre le volte e se indirizzo il flusso filtrato di zlib in un file invece che attraverso la coppia di socket, i dati compressi sono scritti correttamente. Quindi entrambe le parti funzionano correttamente separatamente, ma non insieme. È un bug PHP che dovrei segnalare o un errore da parte mia?

Questa domanda è derivata da una soluzione a this related question.

risposta

2

Guardando attraverso the C source code, il problema è che il filtro consente sempre a zlib's deflate() function di decidere quanti dati accumulare prima di produrre l'output compresso. Il filtro di svuotamento non crea un nuovo bucket di dati da trasmettere a meno che lo deflate() non emetta alcuni dati (vedere la riga 235) o il bit di contrassegno PSFS_FLAG_FLUSH_CLOSE sia impostato (riga 250). Ecco perché vedi solo i byte di intestazione finché non chiudi $in; la prima chiamata a deflate() restituisce i due byte di intestazione, quindi data->strm.avail_out è 2 e viene creato un nuovo bucket per questi due byte da passare.

Nota che fflush() non funziona a causa di un problema noto con il filtro zlib. Vedi: Bug #48725 Support for flushing in zlib stream.

Sfortunatamente, non sembra esserci un bel work-around per questo. Ho iniziato a scrivere un filtro in PHP estendendo php_user_filter, ma ho subito riscontrato il problema che php_user_filter non espone i bit di flag, solo se flags & PSFS_FLAG_FLUSH_CLOSE (il quarto parametro del metodo filter(), un argomento booleano comunemente denominato $closing). Dovresti modificare tu stesso le fonti C per correggere il Bug # 48725. In alternativa, riscrivilo.

Personalmente vorrei prendere in considerazione ri-scrittura, perché ci sembra essere alcuni problemi sopracciglio di sensibilizzazione con il codice:

  • status = deflate(&(data->strm), flags & PSFS_FLAG_FLUSH_CLOSE ? Z_FULL_FLUSH : (flags & PSFS_FLAG_FLUSH_INC ? Z_SYNC_FLUSH : Z_NO_FLUSH)); sembra strano perché quando si scrive, non so il motivo per cui flags sarebbe nulla diverso da PSFS_FLAG_NORMAL. È possibile scrivere allo stesso tempo & a filo?In ogni caso, la gestione delle bandiere deve essere eseguita al di fuori del ciclo while attraverso la brigata "in", come se PSFS_FLAG_FLUSH_CLOSE venga gestito al di fuori di questo ciclo.
  • La riga 221, memcpy a data->strm.next_in sembra ignorare il fatto che data->strm.avail_in potrebbe essere diverso da zero, quindi l'output compresso potrebbe saltare alcuni dati di una scrittura. Vedere, per esempio, il seguente testo dal manuale zlib:

    Se non tutti ingresso può essere elaborato (perché non c'è abbastanza spazio nel buffer di uscita), next_in e avail_in sono aggiornate e lavorazione riprenderà a questo punto per la prossima chiamata di deflate().

    In altre parole, è possibile che avail_in non sia zero.

  • L'istruzione if sulla linea 235, if (data->strm.avail_out < data->outbuf_len) dovrebbe essere probabilmente if (data->strm.avail_out) o forse if (data->strm.avail_out > 2).
  • Non so perché *bytes_consumed = consumed; non è *bytes_consumed += consumed;. I flussi di esempio a http://www.php.net/manual/en/function.stream-filter-register.php utilizzano tutti += per aggiornare $consumed.

EDIT:*bytes_consumed = consumed; è corretto. The standard filter implementations utilizza tutti = anziché += per aggiornare il valore size_t indicato dal quinto parametro. Inoltre, anche se $consumed += ... sul lato PHP si traduce in (vedere le righe 206 e 231 di ext/standard/user_filters.c), la funzione di filtro nativa viene chiamata con un puntatore NULL o un puntatore a size_t impostato su 0 per il quinto argomento (vedere le righe 361 e 452 di main/streams/filter.c).

+0

Grazie mille per la spiegazione. Ho implementato lo stesso progetto in Ruby e ho dovuto passare 'Zlib :: SYNC_FLUSH' come secondo argomento a' Zlib :: deflate() 'per farlo funzionare. Suppongo che questo stia scrivendo e poi immediatamente lo scarico dopo la scrittura. Ho notato che PHP sta usando 'Z_SYNC_FLUSH' solo se il flag' PSFS_FLAG_FLUSH_INC' è impostato, ma come hai detto, i bit di flag non sembrano essere esposti. – FtDRbwLXw6

1

È necessario chiudere lo stream dopo la scrittura per svuotarlo prima che i dati entrino dalla lettura.

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

che produce: compressa: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd70300532b079c compressa: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29cacc4bd7512849cc4e552829cfd70300b1b50b07 compressa: 789c0bcecf4d5548ca4c57c8c9cf4b57282e29ca0452ba0a25199945290a259940c9cc62202f55213923b128d71e008e4c108c

Inoltre ho acceso il $ e $ fuori perché scrivendo a $ in me confuso.

+0

Grazie per la risposta, ma questa soluzione non è fattibile.L'overhead per l'apertura/chiusura delle prese dopo ogni scrittura sarebbe proibitivo di per sé, ma sta anche distruggendo il filtro zlib dopo ogni scrittura, il che interromperà l'implementazione. Il punto di utilizzo di un filtro zlib è che le scritture successive utilizzano lo stesso filtro. Sicuramente ci deve essere un modo per lavare senza chiudere? Ho provato 'fflush()' prima, senza molta fortuna. ** Modifica: ** Per essere più chiari, l'invio dell'intestazione ogni scrittura interromperà l'implementazione, in quanto è destinata a essere inviata una sola volta. – FtDRbwLXw6

3

Avevo lavorato sul codice sorgente PHP e ho trovato una soluzione.

di capire cosa succede avevo rintracciato il codice durante un ciclo

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

e ho trovato che la funzione deflate non viene mai chiamato con il set Z_SYNC_FLUSH bandiera perché non nuovi dati sono presenti nella backets_in brigata.

mia correzione è quello di gestire il (PSFS_FLAG_FLUSH_INC flag è impostato AND non iterazioni vengono eseguite sulla funzione sgonfiare caso) che proroga il

if (flags & PSFS_FLAG_FLUSH_CLOSE) { 

gestione FLUSH_INC troppo:

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

This downloadable patch è per debian squeeze versione di PHP ma la versione git corrente del file è più vicina ad essa quindi suppongo che il porto della correzione sia semplicemente (poche righe).

Se si verifica qualche effetto collaterale, contattatemi.

Problemi correlati