QT中利用zlib做gzip压缩

最近有个QT的项目需要实现简单的Http Server,为了节省带宽,返回的数据需要做下gzip压缩。QT中QByteArray自带的qCompress和qUncompress有一些杂七杂八的头部,所以,还是退一步使用zlib吧。。。

按照gzip的RFC 1952,gzip压缩格式包含如下部分:

         +---+---+---+---+---+---+---+---+---+---+
         |ID1|ID2|CM |FLG|     MTIME     |XFL|OS | (more-->)
         +---+---+---+---+---+---+---+---+---+---+
         +=======================+
         |...compressed blocks...| (more-->)
         +=======================+
           0   1   2   3   4   5   6   7
         +---+---+---+---+---+---+---+---+
         |     CRC32     |     ISIZE     |
         +---+---+---+---+---+---+---+---+

其中,compressed blocks采用deflate算法,这与zlib相同。不同的是,gzip的头部、尾部信息与zlib不相同。

在zlib中,因此特别提供了初始化函数

ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy));

其中,windowBits的设置不仅可以决定deflate算法所采用滑动窗的大小,也可以控制输出头的内容。例如,windowBits设置为8~15之间时,自动添加zlib头;在windowsBits设置为负数,即-8~-15时,表示标准deflate格式,不包括zlib头;同时,如果在所选数值上加16(即第4比特置1),会添加gzip头。

不过,尝试了几次24~31和1~8,都没有成功。如果有同学知道,可以告诉我原因。所以,还是手动添加gzip头吧。。。

参考了下Nginx的代码,ngx_http_gzip_filter_module.c中头是这样添加的:

    static u_char  gzheader[10] =
                               { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };

尾部包括两个字段各4字节:crc32和原始数据长度。

贡献一段QT中很乱的代码:

QByteArray TrackerProcessor::gzip(QByteArray in)
{
    QByteArray buf, toReturn;
    int total_out;
    buf.resize(GZIP_BUF_SIZE);

    z_stream stream;
    stream.zalloc = NULL;
    stream.zfree = NULL;
    stream.opaque = NULL;
    stream.data_type = Z_TEXT;
    stream.avail_in = 0;
    stream.next_in = Z_NULL;

    //Use Init2 to remove zlib header
    int ret = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15,
                    MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
    if(Z_OK != ret)
    {
        qDebug() << "faild to init deflate";
        return NULL;
    }

    stream.avail_in = in.size();
    stream.next_in = (Bytef *)(in.data());

    do {
        stream.avail_out = GZIP_BUF_SIZE;
        stream.next_out = (Bytef*) (buf.data());
        ret = deflate(&stream, Z_FINISH);

        qDebug() << "ret: " << ret;

        if(Z_OK == ret && stream.avail_out == 0)
        {
            if( buf.size() > GZIP_BUF_SIZE_MAX )
            {
                qDebug() << "Exceed memory bound in GZIP";
                return NULL;
            }
            buf.resize( buf.size() * 2 );
        }

        if(Z_STREAM_ERROR == ret)
        {
            qDebug() << "Error when do deflate";
            return NULL;
        }
        total_out = stream.total_out;
    } while(ret != Z_STREAM_END);

    deflateEnd(&stream);
    buf.resize(total_out);

    //Add gzip header
    static char gzheader[10] = {0x1f,0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3};
    toReturn.append(gzheader,10);
    toReturn.append(buf);

    //Caculate CRC
    uLong crc = crc32(0L, Z_NULL, 0);
    crc = crc32(crc, (Bytef*)(buf.data()), total_out);

    //Write tail
    char tmp[4];
    memcpy(tmp,&crc, sizeof(crc));
    toReturn.append(tmp,4);
    uLong isize = in.size();
    memcpy(tmp,&isize, sizeof(isize));
    toReturn.append(tmp,4);
    return toReturn;
}

几点特殊说明

1. zlib中deflate的原理
deflate的算法请参照相关文档,主要思路是寻找前后相同的字串,然后用距离替换。字串和距离按照Hoffman方式存储。
在实现上,zlib采用了典型的流式处理:next_in, avail_in和next_out, avail_out,根据deflate()函数ret值和avail_in、avail_out来判断压缩的状态,是否移动或移走。
几个重要的参数:
memLevel、windowBits、flush
2. 效率优化
对PC环境下,类似于http server的应用中,输入数据流一般都 可以考虑以下几个问题:

  • memLevel可以设置为最大
  • windowBits也可以尽量大(因为总体计算量不大)
  • 可以实现一次性输入、输出以得到最佳压缩比
  • 设置stream中的压缩源类型为text能优化计算
Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Permalink: http://zyj.me/article/qt-zlib-compress
Tags:
« »

Comments are closed.

我的微博