我们为需要的定义包含头文件。对stdio.h,要用到fopen(), fread(), fwrite(), feof(), ferror()和fclose(),以执行文件i/o,还有fputs()用来处理错误消息。对string.h,我们使用strcmp()进行命令行参数处理。对asser.h,我们使用assert()宏。对zlib.h,我们使用基本的压缩函数deflateInit(), defalte()和deflateEnd(),以及基本的解压函数inflateInit(), inflate()和inflateEnd()。
#include
#include
#include
#include "zlib.h"
这段难看的代码是为了防止在WIN/MS-DOS系统上出现输入输出数据损坏的问题。如果没有这段代码,上述操作系统会将输入、输出文件视为文本,将文件中的EOF字符转换为其他的形式。这会破坏二进制数据,尤其会使压缩数据不可用。这段代码将输入输出均设置为二进制模式,以禁止EOF转换,SET_BINARY_MODE()将会在main函数开始处用于stdin和stdout上。
#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
# include
# include
# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
#else
# define SET_BINARY_MODE(file)
#endif
CHUNK是向zlib例程输入数据及提取数据的缓冲区大小。缓冲区越大,效率越高,尤其是解压操作inflate()。如果内存足够,尽量使用128K、256K之类的大小。
#define CHUNK 16384
def()函数压缩源文件的数据到目标文件。输出文件是zlib格式,与gzip和zip格式不同。zlib格式只有一个2字节的头部,标识这是一个zlib流,并提供解压信息;还有一个4字节的尾部,是用来在解压后校验数据完整性的。
/* Compress from file source to file dest until EOF on source.
def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_STREAM_ERROR if an invalid compression
level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
version of the library linked do not match, or Z_ERRNO if there is
an error reading or writing the files. */
int def(FILE *source, FILE *dest, int level)
{
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
首先我们要用deflateInit()初始化zlib状态。strm结构中的zalloc, zfree, opaque必须在调用deflateInit()之前初始化。本例中都设为了Z_NULL,要求zlib使用默认的内存分配策略。应用程序也可以在这里使用定制的内存分配策略。deflateInit()会为内部状态分配256K字节大小的内存。deflateInit()有两个参数,一个是要初始化的结构的指针(strm),一个是压缩的等级(level)。level的值在-1~9之间。压缩等级越低,执行速度越快,压缩率越低。常量Z_DEFAULT_COMPRESSION(等于-1)表示在压缩率和速度方面寻求平衡,实际上和等级6一样。等级0实际上不做任何压缩,只对输入数据做略微改变以形成zlib格式(并不是简单的一字节一字节的拷贝)。高级程序可以在这里使用deflateInit2()以降低内存消耗,但是同时要付出压缩率的代价;或者使用gzip头部和尾部来代替zlib;或者不要头尾部而使用原始编码。
/* allocate deflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level);
if (ret != Z_OK)
return ret;
外层的do-while循环读入所有的输入数据,如果读到了文件结尾则结束循环。这个循环里面只调用了函数deflate()。我们必须确保所有的输入数据都被处理而且所有的输出数据都被输出了(此例中是写入输出文件),然后才可以退出循环。
/* compress until end of file */
do {
strm.avail_in = 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;
/* run deflate() on input until output buffer not full, finish
compression if all of source has been read in */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush); /* no bad return value */
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
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);
assert(strm.avail_in == 0); /* all input will be used */
/* done when last data in file processed */
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END); /* stream will be complete */
/* clean up and return */
(void)deflateEnd(&strm);
return Z_OK;
}
deflate()有两个参数:z_stream结构的参数保存了输入输出信息和内部压缩引擎的状态;int类型的flush参数指明了是否以及如何将数据flush到输出缓冲区。通常,为了加速压缩,deflate会处理K字节的输入数据,然后才产生输出(头部除外)。deflate会突然输出一段压缩后的数据,然后再获取更多输入,然后再突然输出。最后,必须告知deflate()停止读取新的数据,将已读取的输入数据进行压缩,输出,并加上最后的尾部的校验。只要flush参数是Z_NO_FLUSH ,deflate()会一直进行压缩。一旦flush参数变为Z_FINISH,deflate()会开始结束压缩过程。但是,取决于输出缓冲区的大小,即使已经读取完所有输入,仍可能要多次调用deflate()才能使其完成全部压缩输出。在这些连续调用过程中,flush参数必须保持为Z_FINISH。在高级应用程序中,flush参数还可以有其他值。
/* Decompress from file source to file dest until stream ends or EOF.
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
allocated for processing, Z_DATA_ERROR if the deflate data is
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
the version of the library linked do not match, or Z_ERRNO if there
is an error reading or writing the files. */
int inf(FILE *source, FILE *dest)
{
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];
/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit(&strm);
if (ret != Z_OK)
return ret;
外层循环以inflate()是否返回Z_STREAM_END作为循环终止条件。因为如果inflate()返回Z_STREAM_END,说明输入已读完,而且所有输出都产生了(这里和deflate()不同,和flush参数是否为Z_FINISH无关。deflate()返回Z_STREAM_END说明输入已读完,而且如果设置了Z_FINISH的话,所有输出都会产生)。这个和def()相反,def()是判断输入是否已经读完,inf()是判断输出是否已经全部做了。
/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
if (strm.avail_in == 0)
break;
strm.next_in = in;
/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
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);
/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);
/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
内层循环和def()类似,不断调用inflate()直到给定输入被处理完,产生的所有输出都做了。和def()中一样,每次调用inflate()都先提供输出缓冲区,然后可以跑解压引擎了。不需要设置flush参数,因为zlib格式是自终止的(这里不明白)。主要的不同在于,需要注意inflate()的返回值。Z_DATA_ERROR说明读取的数据有错,要么不是zlib格式的,要么某个地方数据有错误。Z_MEM_ERROR说明内存不足。deflate()里的内存在deflateInit()中就被分配好了,而inflate()的内存分配是延迟的,inflate()需要才分配。
/* report a zlib or i/o error */
void zerr(int ret)
{
fputs("zpipe: ", stderr);
switch (ret) {
case Z_ERRNO:
if (ferror(stdin))
fputs("error reading stdin\n", stderr);
if (ferror(stdout))
fputs("error writing stdout\n", stderr);
break;
case Z_STREAM_ERROR:
fputs("invalid compression level\n", stderr);
break;
case Z_DATA_ERROR:
fputs("invalid or incomplete deflate data\n", stderr);
break;
case Z_MEM_ERROR:
fputs("out of memory\n", stderr);
break;
case Z_VERSION_ERROR:
fputs("zlib version mismatch!\n", stderr);
}
}
这里是main()程序,用来测试def()和inf()。zpipe命令只是一个简单的从stdin到stdout的压缩管道,如果没指定参数,则做压缩操作,如果指定-d参数,则是一个解压工具。其他情况则打印帮助信息。例如zpipe < foo.txt > foo.txt.z执行压缩,zpipe -d < foo.txt.z > foo.txt执行解压。
/* compress or decompress from stdin to stdout */
int main(int argc, char **argv)
{
int ret;
/* avoid end-of-line conversions */
SET_BINARY_MODE(stdin);
SET_BINARY_MODE(stdout);
/* do compression if no arguments */
if (argc == 1) {
ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* do decompression if -d specified */
else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
ret = inf(stdin, stdout);
if (ret != Z_OK)
zerr(ret);
return ret;
}
/* otherwise, report usage */
else {
fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
return 1;
}
}
Linux下编译zpipe程序:先编译zlib库(./configure; make test),再切换到examples/目录,运行gcc -O3 zpipe.c -D_LARGEFILE64_SOURCE=1 -DHAVE_HIDDEN -o zpipe -L.. ../libz.a。