zlib是提供数据压缩用的函式库,最早是由Jean-loup Gailly与Mark Adler所开发。今天,zlib是一种事实上的业界标准,以至于在标准文档中,zlib和DEFLATE常常互换使用。数以万计的应用程序直接或间接依靠zlib压缩函式库,包括: Linux核心、libpng、Apache、nginx等等。
可以通过 zlib官网 去下载最新的源代码
这里附上一份 zlib-1.2.11 源代码
在centos7下可以通过一下命令去安装zlib库, 其他Linux发行版或者其他系统的安装方式可以自行查照百度,或者直接使用源代码进行安装
yum install -y zlib zlib-devel
zlib官方的API中压缩使用deflateInit或者deflateInit2,解压使用inflateInit或者inflateInit2,推荐使用后者,后续我们也是根据后者来对zlib进行一个封装。具体函数如下(位于源代码 zlib.h 文件中):
可以看到,事实上上面几个函数只是一个宏,真正的函数是 deflateInit_、inflateInit_、deflateInit2_、inflateInit2_这四个,压缩的函数位于inflate.c 中,解压的函数位于 deflate.c 文件中。
从图中可以看到 deflateInit2 有6个参数,分别是:
strm::zlib压缩流,一个结构体,里面包含了压缩和解压的信息
level,:压缩等级(-1: 使用默认压缩等级,源码中是6, 0: 不压缩, 正常1~9, 数值越大压缩强度越大)
method,:压缩方法(填默认值Z_DEFLATED就行了)
windowBits:类似于选择模式(-(15 ~ 8) : 纯deflate压缩,8 ~ 15 : 带zlib头和尾, > 15 : 带gzip头和尾)
memLevel:运行过程中的内存限制(>=1 && <= 9)
strategy:压缩策略,有5中可以选择,一般默认的Z_DEFAULT_STRATEGY即可(Z_FILTERED,Z_HUFFMAN_ONLY,Z_RLE,Z_FIXED,Z_DEFAULT_STRATEGY)
而 inflateInit2 相对简单一点,只有两个参数,分别是 strm 和 windowBits,含义同上。
z_stream 结构体定义如图,也是在 zlib.h 文件中。
我们可以通过 zalloc、zfree、opaque 这三个参数来设置自己的内存分配器,也可以把他们全部置空,使用系统默认的分配器。于是乎我们可以在类中封装一个初始化函数如下:
int ZlibStream::init(Type type, int level, int window_bits,
int memlevel, Strategy strategy)
{
assert((level >= 0 && level <= 9) || level == -1);
assert((window_bits >= 8 && window_bits <= 15));
assert((memlevel >= 1 && memlevel <= 9));
memset(&m_zstream, 0, sizeof(m_zstream));
m_zstream.zalloc = Z_NULL;
m_zstream.zfree = Z_NULL;
m_zstream.opaque = Z_NULL;
switch (type)
{
case DEFLATE:
window_bits = -window_bits;
break;
case GZIP:
window_bits += 16;
break;
case ZLIB:
default:
break;
}
if (m_encode)
return deflateInit2(&m_zstream, level, Z_DEFLATED, window_bits,
memlevel, (int)strategy);
else
return inflateInit2(&m_zstream, window_bits);
}
在类中添加两个枚举,使得输入参数更为直观
/**
* brief: 压缩类型
*/
enum Type
{
DEFLATE = 0,
ZLIB,
GZIP
};
/**
* brief: 压缩策略
*/
enum Strategy
{
DEFAULT = Z_DEFAULT_STRATEGY,
FILTERED = Z_FILTERED,
HUFFMAN = Z_HUFFMAN_ONLY,
RLE = Z_RLE,
FIXED = Z_FIXED
};
代码如下,其中主要使用了 deflate 和 inflate 函数,这两个函数的第一个参数都是刚刚初始化的那个 z_stream 结构体,第二个参数可以可以是 Z_NO_FLUSH 和 Z_FINISH,前者表示还有数据要压缩,后者表示已无数据需要压缩,一般用于压缩结尾输出最后的压缩内容。
我们一开始需要设置 z_stream 的 next_in(指向压缩数据的指针)和 avail_in(压缩数据的长度),而 next_out 则是设置为指向我们输出结果的内存块,相应的 avail_out 则是该内存块的大小。每次调用 deflate 或者 inflate 之后,avail_out 会变成输出内存块剩余的空间大小,在代码中我们也是根据这个值是否为0来判断当次压缩是否完成的。
int ZlibStream::execute(bool encode, const std::vector<iovec>& v)
{
int ret = 0;
for (size_t i = 0; i < v.size(); i++)
{
m_zstream.avail_in = v[i].iov_len;
m_zstream.next_in = (Bytef*)v[i].iov_base;
iovec* ivc = nullptr;
do
{
if (m_buffers.empty() || m_buffers.back().iov_len == m_buffSize)
{
iovec vc;
vc.iov_base = malloc(m_buffSize);
vc.iov_len = 0;
m_buffers.push_back(vc);
}
ivc = &m_buffers.back();
m_zstream.avail_out = m_buffSize - ivc->iov_len;
m_zstream.next_out = (Bytef*)ivc->iov_base + ivc->iov_len;
if (m_encode)
ret = deflate(&m_zstream, Z_NO_FLUSH);
else
ret = inflate(&m_zstream, Z_NO_FLUSH);
if (ret == Z_STREAM_ERROR)
return ret;
ivc->iov_len = m_buffSize - m_zstream.avail_out;
}
while (m_zstream.avail_out == 0);
}
return Z_OK;
}
代码如下,和之前的压缩执行函数基本一致,只是此时已无数据需要压缩了,则将next_in 和 avail_in 置空,deflate 和 inflate 的第二个参数改为 Z_FINISH,在最后调用 deflateEnd 或者 inflateEnd 来结束本次压缩/解压过程,最终的数据也顺利输出到我们的缓冲区中。(注:输出最后的数据时有可能会出现输出内存不够的情况,所以依然需要循环判断 avail_out 是否用完,这一步是不能省略的)
int ZlibStream::flush()
{
int ret = 0;
m_zstream.avail_in = 0;
m_zstream.next_in = nullptr;
iovec* ivc = nullptr;
do
{
if (m_buffers.empty() || m_buffers.back().iov_len == m_buffSize)
{
iovec vc;
vc.iov_base = malloc(m_buffSize);
vc.iov_len = 0;
m_buffers.push_back(vc);
}
iovec* ivc = &m_buffers.back();
m_zstream.avail_out = m_buffSize - ivc->iov_len;
m_zstream.next_out = (Bytef*)ivc->iov_base + ivc->iov_len;
if (m_encode)
ret = deflate(&m_zstream, Z_FINISH);
else
ret = inflate(&m_zstream, Z_FINISH);
if (ret == Z_STREAM_ERROR)
return ret;
ivc->iov_len = m_buffSize - m_zstream.avail_out;
}
while (m_zstream.avail_out == 0);
if (m_encode)
deflateEnd(&m_zstream);
else
inflateEnd(&m_zstream);
return Z_OK;
}
zlib库使用起来还是比较简单,有兴趣的可以去看一下官方的源代码。
这里给出本文的源代码,里面包含一个简单的测试文件,Linux环境下在根目录make即可完成代码编译,然后输入 ./all 执行测试代码(需实现安装g++、make和zlib等依赖环境)
c++zlib简单封装源代码