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 计数:
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 上阅读或评论(发表评论需要登录)