最近有个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能优化计算
