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 封装。使用 -15 调用 inflateInit2() 接受原始 DEFLATE, 使用 15 + 16 接受 gzip,使用 15 + 32 启用 zlib 或 gzip 自动检测。提供输出空间, 并调用 inflate(),直到输入耗尽、输出已满或 zlib 返回终止状态。通过 inflateSetDictionary() 提供封装标识的预置字典来处理 Z_NEED_DICT;不要从无关输入中猜测字典。

在 Z_STREAM_END 时,avail_in 可能非零:这些字节属于已完成流之后的内容,不能静默丢弃。对于串联的 gzip 成员,记录剩余输入,重置或重新初始化解压器,并在验证每个成员尾部的同时处理每个成员。第一个成员成功并不能证明尾随字节是有效的 gzip 数据。原始 DEFLATE 没有封装校验和;zlib 会验证 Adler-32,gzip 会验证 CRC-32 和 ISIZE。

解压可能会大幅扩展数据。在接受不可信数据前,应强制执行输出量、分配量、压缩比或工作量,以及截止时间限制。拒绝 Z_DATA_ERROR、截断输入、无效距离和违反输出限制的情况。不要将错误发生前生成的字节视为完整或已认证的应用数据。

清理和更高层 API #

每次成功初始化都要准确调用一次 inflateEnd() 或 deflateEnd()。这些函数会释放内部 分配,但不会刷新待处理输出。compress() 和 uncompress() 适用于有界的一次性缓冲区; gzopen() 和 gzread() 提供 gzip 文件访问。当输入大小未知、输出必须有界或协议需要明确 的刷新边界时,应使用流式 API。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)