zlib 串流 API

最後更新: 2024-04-03 14:47:29 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

zlib 是實作 DEFLATE 及其常見包裝格式的 C 函式庫。其串流 API 使用 z_stream,因此應用程式可以在有界緩衝區中處理輸入與輸出,而不必假設一次呼叫會消耗或產生完整訊息。

串流狀態與初始化 #

應用程式提供 next_in/avail_in 與 next_out/avail_out。呼叫後,zlib 會前移指標並減少可用計數;total_in 與 total_out 提供累計計數器。應用程式必須消耗產生的位元組,並在下一次呼叫前補充緩衝區,同時將其他狀態欄位交由 zlib 管理。

deflateInit() 會產生 zlib 包裝的串流。deflateInit2() 使用負的 windowBits 選取原始 DEFLATE,或使用 windowBits + 16 選取 gzip。初始化前先設定配置器欄位,並在每個成功初始化的串流上呼叫 deflateEnd(),包括錯誤路徑。壓縮器可以使用 deflateSetDictionary() 預先載入共用歷史。zlib 包裝會攜帶 Adler-32 字典識別碼,並讓 inflate() 回傳 Z_NEED_DICT;原始 DEFLATE 要求外層協定識別字典。gzip 包裝不定義預設字典訊號。

正確的 Deflate 迴圈 #

一般輸入使用 Z_NO_FLUSH;頻繁清空會降低壓縮率。只要仍有輸入或輸出待處理,就持續呼叫 deflate()。要完成時停止提供輸入,並以 deflate(..., Z_FINISH) 呼叫到回傳 Z_STREAM_END,在呼叫之間擴大或排空輸出緩衝區。以下有界輸入草圖假設 input_len 符合 zlib 的 uInt 計數:

c
z_stream strm = {0};
if (deflateInit(&strm, Z_DEFAULT_COMPRESSION) != Z_OK) fail();
strm.next_in = input;
strm.avail_in = (uInt)input_len;

for (;;) {
    strm.next_out = out;
    strm.avail_out = sizeof out;
    uInt input_before = strm.avail_in;
    int flush = strm.avail_in == 0 ? Z_FINISH : Z_NO_FLUSH;
    int rc = deflate(&strm, flush);
    size_t produced = sizeof out - strm.avail_out;
    write_bytes(out, produced);
    if (rc == Z_STREAM_END) break;
    if (rc != Z_OK) fail(rc);
    if (input_before == strm.avail_in && produced == 0) fail_no_progress();
}
deflateEnd(&strm);

實際程式碼必須在下一次 Z_NO_FLUSH 呼叫前補充 next_in,並追蹤兩個游標的進度。Z_BUF_ERROR 可能表示因緩衝區為空或已滿而無法取得進度;它本身不代表資料損毀。Z_SYNC_FLUSH 會讓解碼器取得資料但不結束串流,而 Z_FINISH 會結束串流。

對網路或檔案讀取器而言,將每個新接收的區塊指派給 next_in/avail_in,呼叫直到該計數歸零或輸出緩衝區已滿,然後排空輸出並繼續。當 zlib 仍在 avail_in 中回報某個區塊時,不要覆寫它;該游標就是擁有權邊界。

具界限的 Inflating #

inflateInit() 接受 zlib 包裝。inflateInit2() 搭配 -15 接受原始 DEFLATE,搭配 15 + 16 接受 gzip,搭配 15 + 32 啟用 zlib 或 gzip 自動偵測。提供輸出空間,並呼叫 inflate() 直到輸入耗盡、輸出已滿,或 zlib 回傳終止狀態。以 inflateSetDictionary() 提供由包裝識別的預設字典來處理 Z_NEED_DICT;不要從無關輸入猜測字典。

在 Z_STREAM_END 時,avail_in 可能非零:這些位元組位於已完成串流之後,不得靜默丟棄。對串接的 gzip 成員,記錄剩餘輸入,重設或重新初始化 inflater,並在驗證每個成員結尾時處理每個成員。第一個成員成功不代表尾端位元組是有效的 gzip 資料。原始 DEFLATE 沒有包裝校驗和;zlib 會驗證 Adler-32,而 gzip 會驗證 CRC-32 與 ISIZE。

解壓縮可能大幅擴張。接受不受信任的資料前,先強制執行輸出、配置、比例或工作量,以及期限限制。拒絕 Z_DATA_ERROR、截斷輸入、無效距離與超出輸出限制的情況。不要將錯誤前產生的位元組視為完整或已驗證的應用程式資料。

清理與較高階 API #

每次成功初始化都要恰好呼叫一次 inflateEnd() 或 deflateEnd()。這些函式會釋放內部配置,但不會清空待處理輸出。compress() 與 uncompress() 適合有界的一次性緩衝區;gzopen() 與 gzread() 提供 gzip 檔案存取。當輸入大小未知、輸出必須有界,或協定需要明確清空邊界時,使用串流 API。

參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)