Libevent 学习十三:zlib 压缩通信

本文介绍了zlib库在C++中对数据流进行压缩和解压缩的实例,包括针对文件的数据流压缩函数def和解压缩函数inf的使用,以及如何处理压缩和解压缩过程中的内存和输出缓冲区。示例代码展示了如何利用zlib对文件进行压缩和解压缩操作,并提供了针对字节流的compress和uncompress函数的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

zlib 简介

zlib 是一个非常著名的数据压缩工具,而且是开源的,由 Jean-loup GaillyMark Adler 所开发。它能使用很少的系统资源,对各种数据都有很好的压缩效果,而且接口一直很稳定,被广泛用于各种项目中。

zlib 官网:https://zlib.net/


zlib 实例一:针对数据流的压缩

zlib 官方的 example 中提供了1个很好的数据压缩和解压缩的实例 zpipe.c ,代码很经典,但个人感觉使用起来有一些不太直观,以下是官方的 example code(本人添加了注释)

/* zpipe.c: zlib 库中压缩函数 deflate() 和解压缩函数 inflate() 的正确使用 */

#include <iostream>
#include <cstring>
#include <cstdio>
#include <assert.h>
#include "zlib.h"

// 避免行尾转换,只在Windows需要设置,不用管
#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
#  include <fcntl.h>
#  include <io.h>
#  define SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY)
#else
#  define SET_BINARY_MODE(file)
#endif

#define CHUNK 16384

/**
 * 从源文件中读取数据并压缩,写入到目标文件中,读取到 EOF 结束
 * 返回:
 *      Z_OK:            成功
 *      Z_MEM_ERROR:     内存不足
 *      Z_STREAM_ERROR:  无效的压缩级别
 *      Z_VERSION_ERROR: 版本错误
 *      Z_ERRNO:         读写文件错误
 */
int def(FILE *source, FILE *dest, int level)
{
    int ret, flush;
    z_stream strm;
    Byte in[CHUNK], out[CHUNK];

    // 初始化压缩的参数
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;

    // 初始化压缩stream
    ret = deflateInit(&strm, level);
    if (ret != Z_OK) return ret;

    // 压缩,直至文件结束
    do {
        strm.avail_in = (uInt)fread(in, 1, CHUNK, source);
        if (ferror(source)) {       //读取错误
            (void)deflateEnd(&strm);
            return Z_ERRNO;
        }
        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
        strm.next_in = in;          // 输入数据起始地址,只操作指针,不拷贝数据

        // 调用 deflate 压缩数据,直到所有输入数据被处理完
        // strm.avail_out == 0 表示输出空间用完,但还有未压缩的数据,需要继续压缩
        do {
            strm.avail_out = CHUNK; // 输出空间剩余缓存大小
            strm.next_out = out;
            ret = deflate(&strm, flush);
            assert(ret != Z_STREAM_ERROR);
            int have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)deflateEnd(&strm);
                return Z_ERRNO;
            }
        } while (strm.avail_out == 0);
    } while (flush != Z_FINISH);
    assert(ret == Z_STREAM_END);    // stream 已被压缩完成

    // 清理 stream 并返回
    (void)deflateEnd(&strm);
    return Z_OK;
}

/**
 * 从源文件中读取数据并解压缩,写入到目标文件中,读取到 EOF 结束
 * 解压缩是还原数据,没有级别限制
 * 返回:
 *      Z_OK:            成功
 *      Z_MEM_ERROR:     内存不足
 *      Z_STREAM_ERROR:  无效的压缩级别
 *      Z_VERSION_ERROR: 版本错误
 *      Z_ERRNO:         读写文件错误
 */
int inf(FILE *source, FILE *dest)
{
    int ret;
    z_stream strm;
    Byte in[CHUNK], out[CHUNK];

    // 初始化压缩的参数
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;

    // 初始化解压缩stream
    ret = inflateInit(&strm);
    if (ret != Z_OK) return ret;

    // 解压缩,直至文件结束
    do {
        strm.avail_in = (uInt)fread(in, 1, CHUNK, source);
        if (ferror(source)) {       //读取错误
            (void)inflateEnd(&strm);
            return Z_ERRNO;
        }
        if (strm.avail_in == 0) break;
        strm.next_in = in;          // 输入数据起始地址,只操作指针,不拷贝数据

        // 调用 inflate 解压缩数据,直到所有输入数据被处理完
        // strm.avail_out == 0 表示输出空间用完,但还有未解压的数据,需要继续解压
        do {
            strm.avail_out = CHUNK; // 输出空间剩余缓存大小
            strm.next_out = out;
            ret = inflate(&strm, Z_NO_FLUSH);
            assert(ret != Z_STREAM_ERROR);

            if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
                (void)inflateEnd(&strm);
                return Z_ERRNO;
            }

            int have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)inflateEnd(&strm);
                return Z_ERRNO;
            }
        } while (strm.avail_out == 0);
    } while (ret != Z_STREAM_END);

    // 清理 stream 并返回,需要判断数据是否解压缩完,数据错误会导致无法解压缩
    (void)inflateEnd(&strm);
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}

/* 打印错误信息 */
void zerrmsg(int ret)
{
    std::cerr << "zpipe: ";
    switch (ret) {
    case Z_ERRNO:
        if (ferror(stdin))
            std::cerr << "error reading stdin";
        if (ferror(stdout))
            std::cerr << "error writing stdout";
        break;
    case Z_STREAM_ERROR:
        std::cerr << "invalid compression level";
        break;
    case Z_DATA_ERROR:
        std::cerr << "invalid or incomplete deflate data";
        break;
    case Z_MEM_ERROR:
        std::cerr << "out of memory";
        break;
    case Z_VERSION_ERROR:
        std::cerr << "zlib version mismatch!";
    }
    std::cerr << std::endl;
}

int main(int argc, char* argv[])
{
    int ret;
    // 避免行尾转换
    SET_BINARY_MODE(stdin);
    SET_BINARY_MODE(stdout);

    // -d 表示解压缩
    if (argc == 1) {
        ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
        if (ret != Z_OK) zerrmsg(ret);
        return ret;
    }
    else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
        ret = inf(stdin, stdout);
        if (ret != Z_OK) zerrmsg(ret);
        return ret;
    }
    else {
        std::cerr << "zpipe usage: zpipe [-d] < source > dest" << std::endl;
        return 1;
    }

    return 0;
}

在 VS 2015 上编译通过,可以先使用一个文本文件 100.txt 进行测试,文件这的内容如下,总共有 1000 个字符。

12345678901234567890123456789012345678901234567890
......
12345678901234567890123456789012345678901234567890

使用如下命令进行压缩和解压缩:

# 压缩
$ zpipe.exe < 100.txt > 100.txt.zlib

# 解压缩
$ zpipe.exe -d < 100.txt.zlib > 100.txt

这里需要解释以下,因为压缩函数 def 和解压缩函数 inf 的都是传入的标准输入输出(stdin 和 stdout),所以需要使用重定向才能读取和写入文件,这就是前面说的,使用上不太直观的地方,除此之外,压缩和解压缩的调用都很经典。

注意,z_stream 中的 avail_out 在函数调用过程中值会改变,用户需要根据这个值来判断压缩和解压缩是否完成,可以将它当作是一个输出参数。

压缩后的文件 100.txt.zlib 是一串二进制乱码,只有 34 个字节,可以看出,zlib对文本文件的压缩率还是很高的。

x?426153钒40$捙薊Q=F?-= L宋?

实例二:针对字节的压缩

若是只需要压缩一串简单的字节流,则可以直接调用 compressuncompress 函数,方法如下:

#include <iostream>
#include <cstring>
#include "zlib.h"

// 压缩和解压缩测试
void test_compress_uncompress(const char* str, size_t len)
{
    Byte compr[1024], uncompr[1024];
    uLong compr_len, uncompr_len;

    // 压缩
    compr_len = sizeof(compr);      // 字符串的结束符也要压缩
    int err = compress(compr, &compr_len, (Byte*)str, (uLong)len + 1);
    if (err != Z_OK) {
        std::cerr << "compress error: " << err << std::endl;
        exit(1);
    }
    std::cout << "orignal size: " << len + 1
        << ", compress size: " << compr_len << std::endl;

    // 解压缩
    uncompr_len = sizeof(uncompr);;
    err = uncompress(uncompr, &uncompr_len, compr, compr_len);
    if (err != Z_OK) {
        std::cerr << "uncompress error: " << err << std::endl;
        exit(1);
    }
    std::cout << "orignal size: " << compr_len 
        << ", uncompress size: " << uncompr_len << std::endl;

    // 判断解压缩后的数据和原始数据是否相同
    if (std::strcmp(str, (const char*)uncompr)) {
        std::cerr << "bad compress" << std::endl;
        exit(1);
    }
    std::cout << "uncompress: " << uncompr << std::endl;
}

int main(int argc, char* argv[])
{
    const char* str1 = "1234567890123456789012345678901234567890";
    const char* str2 = "ajsdfljasfu928 asdfjaslkd sadfjaskljlasd";

    test_compress_uncompress(str1, std::strlen(str1));
    test_compress_uncompress(str2, std::strlen(str2));

    return 0;
}

compress/uncompress 的函数定义如下:

/**
 * 压缩函数,使用默认压缩等级 Z_DEFAULT_COMPRESSION
 * 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存
 */
int compress(Bytef *dest, uLongf *destLen,
             const Bytef *source, uLong sourceLen);

/**
 * 压缩函数2,可以手动指定压缩等级
 * 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
 *      Z_STREAM_ERROR 压缩 level 参数非法
 */
int compress2(Bytef *dest,   uLongf *destLen,
              const Bytef *source, uLong sourceLen,
              int level);

/**
 * 解压缩函数,需要预留足够的输出缓存(解压数据缓存)
 * 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
 *      Z_DATA_ERROR 输入数据错误或不完整
 */
int uncompress(Bytef *dest,   uLongf *destLen,
               const Bytef *source, uLong sourceLen);

/**
 * 解压缩函数2,sourceLen 为指针,表示解压时消耗的源字节数,此时可能由于输出缓存不足,导致数据并没有被解压完
 * 返回:Z_OK 成功;Z_MEM_ERROR 没有足够的内存;Z_BUF_ERROR 没有足够的输出缓存;
 *      Z_DATA_ERROR 输入数据错误或不完整
 */
int uncompress2(Bytef *dest,   uLongf *destLen,
                const Bytef *source, uLong *sourceLen);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值