zpipe解说

来源:互联网 发布:mysql删除and语句 编辑:程序博客网 时间:2024/04/27 20:47

一.问题背景

程序中碰到gzip解压问题了。问题是这样的:压缩包分成了n个发送,接收端需要及时从每个包中查找关键词。从网上收的一篇关于C++ GZIP解压的程序,于是苦逼了:每次收的新的子包都需跟前面的包拼接后解压,然后搜索。结果程序遇到多包时cpu就跑死了。跟zlib官网发邮件询问,苦于英语水平,问题也没得到解决。于是就把zlib官网看了一遍。皇天不负有心人,总于找到一个例子。现做翻译,供大家参考(限于英语水平有限,错误在所难免)。

二.解说

1.先对压缩流数据结构做个说明:
typedef struct z_stream_s {
    z_const Bytef *next_in;     /* 下一个输入的字节 */
    uInt     avail_in;  /*  next_in中可用的字节总数,即还没处理的字节总数 */
    uLong    total_in;  /* 当前读入的字节总数 */


    Bytef    *next_out; /* 即将输出的字节,next output byte should be put there */
    uInt     avail_out; /* next_out还剩余的空间总数 */
    uLong    total_out; /* 当前输出的字节总数*/


    z_const char *msg;  /* last error message, NULL if no error */
    struct internal_state FAR *state; /* not visible by applications */


    alloc_func zalloc;  /* used to allocate the internal state */
    free_func  zfree;   /* used to free the internal state */
    voidpf     opaque;  /* private data object passed to zalloc and zfree */


    int     data_type;  /* best guess about the data type: binary or text */
    uLong   adler;      /* adler32 value of the uncompressed data */
    uLong   reserved;   /* reserved for future use */
} z_stream;

2.gzip数据头
typedef struct gz_header_s {
    int     text;       /* true if compressed data believed to be text */
    uLong   time;       /* modification time */
    int     xflags;     /* extra flags (not used when writing a gzip file) */
    int     os;         /* operating system */
    Bytef   *extra;     /* pointer to extra field or Z_NULL if none */
    uInt    extra_len;  /* extra field length (valid if extra != Z_NULL) */
    uInt    extra_max;  /* space at extra (only when reading header) */
    Bytef   *name;      /* pointer to zero-terminated file name or Z_NULL */
    uInt    name_max;   /* space at name (only when reading header) */
    Bytef   *comment;   /* pointer to zero-terminated comment or Z_NULL */
    uInt    comm_max;   /* space at comment (only when reading header) */
    int     hcrc;       /* true if there was or will be a header crc */
    int     done;       /* true when done reading gzip header (not used
                           when writing a gzip file) */
} gz_header;

3.言归正传

我们经常遇到一些怎样去用 deflate() 和 inflate() 这两个函数的问题. 使用者想问在他们有多个输入和多个输出时碰到Z_BUF_ERROR怎样去处理,怎样确保进程合理结束等等问题。
对于那些已经读过zlib.h,并想获取进一步的提示的使用者来说,下边提供了一个用C编写的注解例子,用来分别演示怎样调用deflate() 和 inflate()来对文件进行压缩和解压。
代码行之间有注释,所以阅读一下,希望能帮助理解zlib的难以理解之处.
闲话少说,下面是zpipe.c: 




/* zpipe.c: example of proper use of zlib's inflate() and deflate()
   Not copyrighted -- provided to the public domain
   Version 1.4  11 December 2005  Mark Adler */


/* Version history:
   1.0  30 Oct 2004  First version
   1.1   8 Nov 2004  Add void casting for unused return values
                     Use switch statement for inflate() return values
   1.2   9 Nov 2004  Add assertions to document zlib guarantees
   1.3   6 Apr 2005  Remove incorrect assertion in inf()
   1.4  11 Dec 2005  Add hack to avoid MSDOS end-of-line conversions
                     Avoid some compiler warnings for input and output buffers
 */
包含需要定义的头文件. From stdio.h we use fopen(), fread(), fwrite(), feof(), ferror(), and fclose() for file i/o,
 and fputs() for error messages. 
  From string.h we use strcmp() for command line argument processing. 
  From assert.h we use the assert() macro. 
  From zlib.h we use the basic compression functions deflateInit(), deflate(), and deflateEnd(), 
 and the basic decompression functions inflateInit(), inflate(), and inflateEnd(). 


#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
This is an ugly hack required to avoid corruption of the input and output data on Windows/MS-DOS systems. Without this, those systems would assume that the input and output files are text, and try to convert the end-of-line characters from one standard to another. That would corrupt binary data, and in particular would render the compressed data unusable. This sets the input and output to binary which suppresses the end-of-line conversions. SET_BINARY_MODE() will be used later on stdin and stdout, at the beginning of main(). 


#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
CHUNK 是zlib程序加载和读取数据缓冲区大小. 缓冲区越大越有效,尤其是对inflate()来说. 
如果内存可用,大约会用到128k或者256k的缓冲区大小.
#define CHUNK 16384
def()函数能够将输入文件压缩成一个输出文件.输出文件为zlib格式,这不同于gzip和zip格式.zlib格式有一个很小的文件头,
其中两个字节用来标示是zlib数据以提供译码信息,
四个字节标示核对值用来验证解压后数据的完整性。


/* 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)
{
这里是def()的局部变量定义. ret为zlib的返回编码(密码). flush实时返回deflate()的刷新状态,当文件接收完后不做更改. 
have是deflate()返回数据的个数.strm结构体用来给zlib程序传递输入和输出的信息,并保存deflate()状态。变量in和out指的是deflate()的输入输出缓存。


    int ret, flush;
    unsigned have;
    z_stream strm;
    unsigned char in[CHUNK];
    unsigned char out[CHUNK];
首先,我们用deflateInit()来初始化zlib。这是在调用deflate()之前必须做的。
调用deflateInit()之前要初始化strm的成员变量zalloc, zfree, 和 opaque。
它们被初始化成Z_NULL,这个时候zlib就会采用默认的内存分配。当然也可以设定内存分配。
内部状态下会为deflateInit()分配256k字节。(参考zlib技术细节)
调用deflateInit(),其中第一参数为初始化的strm,这里用的是指针,第二个参数为压缩等级,为1-9的整数,压缩等级越低压缩越快,但压缩率越低.
反之,压缩率越高处理越慢. zlib有Z_DEFAULT_COMPRESSION, 等于-1, 能够很好处理压缩和速度问题,相当于压缩等级6.
等级0实际上根本不压缩,就是想把数据简单的转化为zlib格式(但不是对输入进行一个字节一个字节的复制). 
这里zlib会用deflateInit2()替换来做更高级的应用. 这种应用可能想以压缩为代价的情况下尽量降低内存占用。
或者想用gzip头和预存信息来代替zlib头和信息,或者就根本不加头和预存信息。
我们必须用Z_OK来比对deflateInit()的返回值,好确定内存分配成功和参数可行。deflateInit()也做了zlib库版本信息的比对,
以确定程序用的库和头文件一致。
这对zlib的运行环境来说相当重要。
注意同一个应用能被不同的zlib数据流并行初始化多次,结构体中的信息状态允许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;
With the pleasantries out of the way, now we can get down to business. 
外面的do循环读取输入文件,直到最后一个文件。
deflate()函数只在这个循环中调用,所以我们应该确定在退出循环之前所有文件都已经处理,所有输出都已经生成。




    /* compress until end of file */
    do {
    从输入文件读取数据开始。读取的字节被直接放到avail_in中,next_in存放了那些字节的指针。也用feof()检测了一下文件是否读取结束。
    如果读取结束,flush赋成Z_FINISH。它将传递给deflate(),来确定需要压缩的数据已经读取结束。否则flush赋成Z_NO_FLUSH,deflate就知道了数据还在读取中。
  如果读取文件失败,则应在返回错误之前调用deflateEnd()来释放zlib存储空间。状态初始化后可以随时调用deflateEnd(),以防止内存泄露。一旦调用,我们在
  对新数据压缩之前必须再次调用deflateInit() (或 deflateInit2())。没对deflateEnd()的返回值进行检测。存储单元分配不会失败。




        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;
        内部do循环传递大量输入数据到deflate(),调用deflate()一直到产生数据输出。一旦不再有新的输出,为确保deflate()处理了所有的输入,可查看
        avail_in是否变为0。
 


        /* run deflate() on input until output buffer not full, finish
           compression if all of source has been read in */
        do {
        通过给avail_out设置输出字节数来给deflate()分配可用的输出空间,用next_out来指向该空间。


            strm.avail_out = CHUNK;
            strm.next_out = out;
        现在调用它本身的压缩引擎deflate()。deflate()用next_in存储了它能处理的avail_in的最大字节,将和avail_out一样多的字节写入next_out。
        这些计数器和指针随着对数据的处理而改变。输入数据处理了多少限制了输出空间的总量.因此内循环可通过每次output空间多增加的部分来判断处理
        的输入数据的大小。由于avail_in和next_in是被deflate()更新的,所以我们无需打乱它们在deflate()调用中直到它们用完。
    deflate()的参数中一个是包含了输入输出信息和内部压缩引擎状态的指向strm的指针,一个是标示了是否需要还是怎样去刷新输出数据的变量。
一般来说,deflate在产生输出数据之前会用掉输入的几k数据(除了头信息),以计算最优的压缩统计信息。接下来就会产生爆发的压缩数据,在下次
爆发之前处理更多的输入数据。最后,必须告知deflate()数据流终止。 输入数据完成压缩后,给trailer写一个校验值。  
  正常情况下,flush如果是Z_NO_FLUSH,deflate()会一直压缩。一旦变成Z_FINISH,deflate()就将会完成压缩。
  然而,根据提供的输出空间的多少,deflate()会被多次调用直到提供了一个完整的压缩数据流,尽管这时已经处理完了所有的输入数据。对那些随后的调用,
  flush参数必须一直是Z_FINISH。高级应用中flush会有其它值。
  可以强迫deflate()产生爆发输出,来编译所有输入数据,尽管它不会做其它用途,例如根据与压缩数据的联系去潜在的控制数据。
(You can force deflate() to produce a burst of output that encodes all of the input data provided so far, even if it wouldn't have otherwise, 
for example to control data latency on a link with compressed data. )
也可以要求deflate()清除少许历史直到某一个点,此时就像下述所列的可以独立的解压,例如为了随机存储应用。
(You can also ask that deflate() do that as well as erase any history up to that point so that what follows can be decompressed independently, 
for example for random access applications.)
以上两个需求都会按照某一数量进行压缩的分解,这个数量依赖于这种需求多久就会请求。

deflate()有个标示错误的返回值,然而这里我们不用它。为什么不用,因为它在这里不会出错。
  让我们通过分析deflate()的返回值,并一个一个的排除它们。
可能出现的返回值是Z_OK, Z_STREAM_END, Z_STREAM_ERROR, 或者 Z_BUF_ERROR.
其中Z_OK就是没问题。Z_STREAM_END也是没问题,但它是最后一次调用的时候返回的。
这是通过给deflate()传递Z_FINISH直到再没有输出数据来确定的。
   Z_STREAM_ERROR只有在没有正确初始化数据流的时候出现,但我们已经正确的初始化了。
    这里判断Z_STREAM_ERROR也没有害处,例如当应用程序的其他部分因疏忽导致保存zlib状态的内存彻底崩溃,就需要核实可行性。
     Z_BUF_ERROR会在下面解释,但是可以确定的说这个只是deflate()能否处理更多的输入和输出的标示。
    当有更多的输出空间和更多的输入时,deflate()会被再次调用。这个代码中就会是这样。
   




            ret = deflate(&strm, flush);    /* no bad return value */
            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
Now we compute how much output deflate() provided on the last call, which is the difference between how much space was provided before the call, and how much output space is still available after the call. Then that data, if any, is written to the output file. We can then reuse the output buffer for the next call of deflate(). Again if there is a file i/o error, we call deflateEnd() before returning to avoid a memory leak. 


            have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)deflateEnd(&strm);
                return Z_ERRNO;
            }
            内部DO循环会一直进行,直到最后deflate()不能再输出。这时deflate()已经处理完了所有的能处理的输入数据。
退出循环后能再用输入数据。输出缓存不再有数据进来,avail_out已大于0,从这方面来说deflate()不再输出。
然而假如deflate()不在输出,但是刚好输出缓存有数据插入,avail_out的值还是0,我们就不能说deflate()处理完了所有能处理的数据。
只要我们知道deflate()还有输出,我们就会再次调用。但是现在deflate()不在输出,avail_out依旧没有达到CHUNK。deflate()调用不能再
处理任何的输入输出,这样它就会返回Z_BUF_ERROR.(明白了吧,我告诉过你我会在后面解释)。然而这根本就不是个问题。
现在终于表明deflate()是真的在运行,我们就退出内部循环来接收更多的输入。
随着flush设置成Z_FINISH,deflate()最后的调用完成。一旦设置成这个值后,再给flush设置其他值,deflate()就会返回Z_STREAM_ERROR。
除非你重新初始化。


有些zlib程序有两个deflate()的循环调用来代替我们的内部循环。第一个循环会把所有数据传给deflate()而不刷新。第二个循环就只设置Z_FINISH
来进行处理。就像你在这个例子中看到的,那样做可避免连续监测flush的当前状态。


        } while (strm.avail_out == 0);
        assert(strm.avail_in == 0);     /* all input will be used */
        核对是否处理完所有的输入文件。那个信息存在flush变量里,我们可以看它是否被设置成Z_FINISH。
如果是的话,就退出外循环。在跑完循环后,确认一下最后deflate()的返回值是否为Z_STREAM_END。


        /* done when last data in file processed */
    } while (flush != Z_FINISH);
    assert(ret == Z_STREAM_END);        /* stream will be complete */
    处理完成,但为了防止内存泄露我们需要解除分配状态。最后,返回Z_OK。


    /* clean up and return */
    (void)deflateEnd(&strm);
    return Z_OK;
}


现在在inf()函数中,为了解压而做一样的事情。inf()从希望解压的文件中读取数据,并将解压完的数据写到输出文件中。
def()中差不多的描述也可以用到inf(),所以这里只将重点放在两者的不同之处。


/* 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)
{
像def()中功能一样的局部变量。唯一不同就是没有flush,因为当流完成时inflate()会从zlib流本身获取。


    int ret;
    unsigned have;
    z_stream strm;
    unsigned char in[CHUNK];
    unsigned char out[CHUNK];
    初始化是一样的,除了没有压缩率,当然,结构体中多了两成员变量需要初始化。
在调用inflateInit()之前,avail_in 和 next_in必须初始化。这是因为,为了inflateInit()使用压缩信息来分配内存,
应用程序有权给zlib流预设起始点。当前版本中,内存分配方法遵从inflate()的第一次调用。然而,对于以后的zlib版本来说,
这些地方必须初始化。因为以后的zlib版本会用这个接口来提供更多的压缩方法。
不管什么情况,inflateInit()函数都不会等到解压完成来调用,所以调用之前,变量avail_out和next_out不需要初始化。
这里avail_in被设置成0,next_in设置成Z_NULL表示还没有输入数据。




    /* 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;
        
    外部do循环解压输入数据直到inflate()标示没有新的压缩数据进入并已产生了所有的解压输出。
相比之下,def()是处理完所有的输入文件。如果最后一个文件在压缩数据自动终止之前到达,压缩数据就是不完整的,
并且会有错误返回。




    /* decompress until deflate stream ends or end of file */
    do {
    我们读取输入数据,并相应的设置strm结构体。如果已经到了输入文件的结尾,就离开循环返回一个错误,因为压缩数据是不完整的。
这里注意,可能读取的数据多于inflate()最终处理的数据,如果输入文件持续插入zlib流。
对于一些应用,zlib流被插入了其它数据,这个程序就需要修改以返回无用的数据,或者至少指明有多少数据是无用的,这样这个应用就
能知道需要处理到zlib流中的哪个地方。
//完成一次读操作(fread())后,如果没有关闭流(fclose()),则指针(FILE * fp)自动向后移动前一次读写的长度,
//不关闭流继续下一次读操作则接着上次的输出继续输出;
        strm.avail_in = fread(in, 1, CHUNK, source);//strm.avail_in为实际读取的数据个数
        if (ferror(source)) {
            (void)inflateEnd(&strm);
            return Z_ERRNO;
        }
        if (strm.avail_in == 0)
            break;
        strm.next_in = in; //指向读取的数据
        内部do循环有与def()中相同的函数,它持续调用,直到产生与输入相对应的所有输出。


        /* run inflate() on input until output buffer not full */
        do {
        就像在def()一样,每次调用inflate()都要提供相似的输出空间。
            strm.avail_out = CHUNK;
            strm.next_out = out;
        现在运行解压引擎。没必要调整flush变量,因为zlib格式是自动终止的。
        这里最主要的区别就是返回值,我们需要注意。
        Z_DATA_ERROR表示inflate()从zlib格式数据中检测到了错误,这意味着
        或者一开始就不是zlib数据流,或者数据在压缩时损毁了。
        返回的另外一个错误是Z_MEM_ERROR,如果一直到inflate()调用时才分配内存,就会报这个错误。
        这不像deflate(),它的内存是在一开始调用deflateInit()时候分配的。
高级应用可以用deflateSetDictionary()来事先给deflate()设定合适的数来改善最初的32k大小的压缩空间。
这就是著名的zlib头,所以inflate()在解压之前就需要提供那个字典序。
没有字典序,就不能正确的解压。
对于这个程序,我们不来解释字典序是什么,所以用Z_DATA_ERROR转而代替Z_NEED_DICT标示。
inflate()也能返回Z_STREAM_ERROR,在这里是不可能,但是能像def()中那样进行核查。
Z_BUF_ERROR 这里也不需要检查,原因同def()一样. 
Z_STREAM_END将检查以供后用. 




            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;
            }
            
            inflate()的输出操作与deflate()一样。
            have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)inflateEnd(&strm);
                return Z_ERRNO;
            }
            就像deflate()一样,内部do循环在当输出缓存不再有数据进入时退出。
这种情况,我们不能断定strm.avail_in变成0,因为压缩数据流可能在文件结束之前结束。


        } while (strm.avail_out == 0);
        外边do循环结束,此时inflate()宣告收取完了zlib输入数据流,已经完成了解压和完整校对,并且提供了所有输出数据。
inflate()用返回值Z_STREAM_END表明。
如果读取的输入文件的最后一个块包含了zlib流的结尾,那么内循环能保证ret等于Z_STREAM_END。
  所以如果返回值不是Z_STREAM_END,循环继续。
 


        /* done when inflate() says it's done */
    } while (ret != Z_STREAM_END);
    这里,解压成功完成,或者因为没有更多的数据读入而跳出循环。
如果最后一次inflate()的返回值不是Z_STREAM_END,那么zlib流就不完整,就返回一个数据错误。
否则返回Z_OK。当然返回前调用inflateEnd(),以防内存泄露。


    /* clean up and return */
    (void)inflateEnd(&strm);
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
直接调用zlib代码结束。接下来写了一个命令行程序,可以通过标准的输入输出来运行数据,并捕获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);
    }
}
这里是用来测试def()和inf()的main()函数。如果参数没有给定,zpipe命令是一个简单的标准输入到输出的压缩管道,否则如果用了
zpipe -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;
    }
}

0 0
原创粉丝点击