2011-09-22 54 views
6

出於某種原因,zlib.deflate過濾器似乎並不與stream_socket_pair()產生插座對合作。所有可以從第二個套接字讀取的是兩個字節的zlib頭,之後的所有內容都是NULL。使用zlib的過濾器與插座對

實施例:

<?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支鏈的。

回答

2

翻看the C source code,問題是過濾器總是讓zlib's deflate() function決定在生成壓縮輸出之前要累積多少數據。除非deflate()輸出一些數據(參見第235行)或PSFS_FLAG_FLUSH_CLOSE標誌位置位(行250),否則放氣過濾器不會創建新的數據存儲桶。這就是爲什麼你只能看到標題字節,直到你關閉$in;對deflate()的第一次調用輸出兩個頭字節,所以data->strm.avail_out爲2,並且爲這兩個字節傳遞創建一個新的存儲桶。

注意fflush()不會因爲與zlib的過濾器的已知問題的工作。參見:Bug #48725 Support for flushing in zlib stream

遺憾的是,似乎沒有成爲一個不錯的工作,圍繞這一點。我開始在PHP中通過擴展php_user_filter來編寫一個過濾器,但是很快遇到了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以外。是否可以同時寫入&?在任何情況下,處理標誌都應該在while循環之外通過「in」桶組來完成,就像PSFS_FLAG_FLUSH_CLOSE在這個循環之外處理一樣。
  • 第221行,memcpydata->strm.next_in似乎忽略了data->strm.avail_in可能不爲零的事實,所以壓縮的輸出可能會跳過某些寫入數據。參見,例如,從ZLIB手冊以下文本:

    如果不是所有輸入可以被處理(因爲沒有足夠的空間在輸出緩衝器中),next_inavail_in被更新和處理將在此恢復指向deflate()的下一個呼叫。

    換句話說,avail_in可能不爲零。

  • 235號線上的if聲明,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;是正確的。 The standard filter implementations全部使用=而不是+=來更新第五個參數指向的值size_t。另外,儘管PHP端的$consumed += ...有效地轉換爲size_t上的+=(參見ext/standard/user_filters.c的第206和231行),但使用NULL指針或指向size_t的指針調用本機過濾器函數,第五個參數爲0 (見main/streams/filter.c的行361和452)。

+0

非常感謝您的解釋。我在Ruby中實現了相同的項目,最後不得不將'Zlib :: SYNC_FLUSH'作爲第二個參數傳遞給'Zlib :: deflate()'以使其工作。我認爲這是寫作,然後在寫完後立即刷新。我注意到如果設置了PSFS_FLAG_FLUSH_INC標誌,PHP只使用'Z_SYNC_FLUSH',但正如你所說的那樣,標誌位似乎沒有被暴露。 – FtDRbwLXw6

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

而且我切換了$並$,因爲在困惑我寫$。

+0

謝謝你的回覆,但這個解決方案並不可行。在每次寫入之後打開/關閉套接字的開銷本身是過高的,但這也會在每次寫入之後破壞zlib過濾器,這會破壞實現。使用zlib過濾器的要點是,連續寫入使用相同的過濾器。當然,必須有辦法沖洗而不關閉?我已經嘗試過'fflush()',沒有多少運氣。 **編輯:**更清楚的是,每寫入一次就發送頭文件會破壞實現,因爲它只能發送一次。 – FtDRbwLXw6

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沒有迭代上放氣功能情況下進行的)延長

if (flags & PSFS_FLAG_FLUSH_CLOSE) { 

管理FLUSH_INC太:

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

This downloadable patchdebian squeeze版本的PHP,但目前的git版本的文件更接近它,​​所以我認爲端口的修復很簡單(幾行)。

如果出現一些副作用,請與我聯繫。