我们常常疑惑不知道怎么样使用deflate()和inflate()。用户想知道应该在什么时候提供更多输入,什么时候使用更多输出,怎么处理Z_BUF_ERROR,怎么确保处理正确地终止,等等。example目录下有一个简单的例程zpipe.c,示范了使用deflate()和inflate()来把输入文件压缩或解压到输出文件。下面对各行代码进行解释。
我们为需要的定义包含头文件。对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 <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#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
def()函数压缩源文件的数据到目标文件。输出文件是zlib格式,与gzip和zip格式不同。zlib格式只有一个2字节的头部,标识这是一个zlib流,并提供解压信息;还有一个4字节的尾部,是用来在解压后校验数据完整性的。
ret保存zlib函数的返回值。flush跟踪deflate()的flushing状态,要么是无flushing,要么在读到输入文件末尾后全部flush。have是deflate()返回的数据字节数。strm是核心结构,用来与zlib例程之间传递信息。in和out是deflate()的输入输出缓冲区。我们必须检查deflateInit()的返回值,如果为Z_OK,则说明内存分配成功,参数合法。deflateInit()还会检查zlib.h头文件所使用的zlib库版本和链接器使用的zlib库版本是否一致,这对于共享zlib库的环境尤为重要。
注意,应用程序可以初始化多个相互独立的zlib流,它们可以并行执行。z_stream结构中保存的状态信息可以让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()。我们必须确保所有的输入数据都被处理而且所有的输出数据都被输出了(此例中是写入输出文件),然后才可以退出循环。
从输入文件读取数据到输入缓冲区,读取的字节数被赋给avail_in,指向这些数据的指针被赋给next_in。用feof检查是否读到了输入文件的文件尾,如果读到了文件尾,那么flush被置为Z_FINISH,flush变量稍后会传递给deflate(),表明这是最后一段要被压缩的输入数据了。如果还没到文件尾,flush被置为Z_NO_FLUSH,表明我们还有未压缩的数据。
如果在读输入文件中遇到错误,结束进程。在结束之前,要调用deflateEnd()释放zlib的状态。deflateEnd()不会出错,不必检查返回值。
内层do-while循环将我们读到的一段输入数据传递给deflate(),然后不停调用deflate()直到输出产生完毕。一旦没有新的输出,也就意味着deflate()已经处理掉了所有的输入,avail_in的值变为0。
先设置输出缓冲区:设置avail_out为可用输出缓冲字节数,next_out为指向缓冲区的指针。然后调用压缩引擎deflate()。它尽可能多地使用next_in指向的长度为avail_in的输入缓冲区中的数据,向next_out指向的输出缓冲区中写入长度为next_out的数据。上述计数器和指针随之更新,跳过已经消耗掉的输入数据和已经写出的输出数据。输出缓冲区的大小可能会限制能消耗多少输入数据。因此内层循环通过每次增加输出缓冲区来确保所有的输入数据都被处理了。因为avail_in和next_in都是由deflate()更新的,在输入缓冲区内的数据被消耗完之前我们不必管它们。(但是因为每次deflate()都输出avail_out的数据,将输出缓冲区填满,所以next_out和avail_out必须由应用程序自己更新)。
deflate()有返回值来告知错误,但是这个例子中我们并不需要检查返回值。为什么?让我们逐一来看deflate()可能的返回值。Z_OK,没有错误。Z_STREAM_END,说明读到输入文件尾部了,但并没有关系,我们的代码连续调用deflate()直到不再产生输出(产生输入已读完,而仍需要调用deflate()进行压缩的原因是输出缓冲区的大小的限制)。Z_STREAM_ERROR只会在流未被正确初始化的情况下出现,但是我们确实正确初始化了。当然,检查一下Z_STREAM_ERROR也没有坏处,因为有可能程序的其他部分不经意地改变了zlib的内存。Z_BUF_ERROR表明deflate()不能再读取更多输入或者产生任何输出了,这种情况下可以通过给予更多输入或者分配更多输出缓冲来再次调用deflate(),下面还会提到Z_BUF_ERROR。
我们现在计算上一次调用deflate()时产生了多少输出,即是调用前分配的输出缓冲区大小减去调用后还剩下的输出缓冲区大小。然后将输出缓冲区的数据写入输出文件。然后下次调用deflate()时又可以重新使用这片输出缓冲区了。如果有文件I/O错误,我们先调用deflateEnd()然后再返回,以免内存泄露。
内层do-while循环直到deflate()不能填满给定的输出缓冲区为止,即剩余可用空间大小不为0(前面提到了,每次deflate()尽可能多地消耗输入数据,但是一次“突然”burst产生的输出数据大小都和输出缓冲区大小一样,除非最后一次产生的输出填不满输出缓冲区)。此时我们知道deflate()已经消耗完了输入缓冲区内的数据,我们可以退出内层循环,然后重新利用这片输入缓冲区。
我们通过看deflate()是否填满了输出缓冲区来判断其是否还有更多的输出没做,如果avail_out大于0,说明输出已经做完了。但是假设这样一种巧合,最后一次的deflate()产生的输出刚好填满了输出缓冲区,avail_out为0,但是deflate()确实已经处理完了所有输入,所有输出也已经做了。这种情况其实没关系,我们再调用一次deflate(),此时会返回Z_BUF_ERROR。
如果flush参数设成Z_FINISH,最后的几次deflate()调用会完成输出流工作。一旦这个做完了,之后的deflate()调用如果flush参数不为Z_FINISH,则会返回Z_STREAM_ERROR,并且不进行任何操作,直到重新初始化z_stream状态。
有些程序用两层循环来代替我们这里的内层循环。第一层循环flush设为Z_NO_FLUSH,将所有输入都读进去,第二层循环将flush设置为Z_FINISH,不再输入,让deflate()完成输出。我们的代码里避免了这样做,因为保持追踪flush的状态更方便。(代码中对应flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;)
我们通过检查flush是否为Z_FINISH(因为flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;)来判断是否还有输入文件数据未被读取。最后一次调用deflate()的返回值必然是Z_STREAM_END,因为所有的输入都已经读完,所有的输出也都产生。整个def()就要结束了,调用deflateEnd防止内存泄露。
下面的inf()是解压。输入数据应该是从输入文件读到的合法zlib流,将解压后的数据写入输出文件。大部分和def()类似。下面主要讲不一样的地方。
状态的初始化是一样的,除了没有level这个参数(当然了,这个得从输入的zlib数据里获取)。z_stream除了要初始化zalloc, zfree ,opaque外,还要初始化next_in和avail_in。这里avail_in设为0,next_in设为Z_NULL,表示没有提供输入数据。
如果在读到压缩文件尾之前读到文件尾EOF,说明压缩数据不完整,结束外部循环,报错。注意读到的数据可能比inflate()最终消耗的数据要多。在这样的程序中,要注意返回未用到的数据,至少也要指明还有多少输入数据没有被inflate()使用,使得程序可以知道从zlib流后面的哪里继续开始。
/* 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;
高级程序中deflate()可能使用了字典(用deflateSetDictionary()设置)进行压缩,那么inflate()就需要使用同样的字典进行解压。如果没有,返回Z_DATA_ERROR。和def()中一样,Z_STREAM_ERROR不可能出现。Z_BUF_ERROR也不必特别处理。
当inflate()没有更多输出的时候,内层循环结束。同样通过看输出缓冲区是否大于0来判断。如果inflate()返回Z_STREAM_END(说明它读到了输入zlib流的尾部,完成了解压和完整性校验,所有的输出已经做了),外层循环结束。内层循环中的inflate()在读到输入文件里有zlib流的尾部时,确保返回Z_STREAM_END,外层循环因此终结。如果不为Z_STREAM_END,继续进行外层循环,读输入文件。
解压完成后,如果外层循环是因为Z_STREAM_END结束的,则顺利结束,否则就是碰到了Z_DATA_ERROR或者Z_MEM_ERROR。当然,我们要调用inflateEnd()以防止内存泄漏。
下面的代码创建命令行程序,通过使用上面的函数从stdin输入到stdout输出,并且处理def()和inf()报告的错误。zerr()用来解析来自def()和inf()的可能错误码,打印一条错误消息。注意这只是deflate()和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执行解压。
Linux下编译zpipe程序:先编译zlib库(./configure; make test),再切换到examples/目录,运行gcc -O3 zpipe.c -D_LARGEFILE64_SOURCE=1 -DHAVE_HIDDEN -o zpipe -L.. ../libz.a。
原文地址:http://blog.csdn.net/zhoudaxia/article/details/8039519