iOS 使用 zlib 庫實現(xiàn)請求數(shù)據(jù)壓縮
1.Content-Encoding
Accept-Encoding 和 Content-Encoding 是 HTTP 中的一對頭部字段青自,用來標識「請求發(fā)送方可接受的響應數(shù)據(jù)編碼格式」橄抹、「請求/響應方發(fā)送的 HTTP Body 編碼格式」楚殿。接收方拿到請求/響應后,根據(jù) Content-Encoding 對應的數(shù)據(jù)格式進行解壓數(shù)據(jù)诗茎。壓縮的目的是為了優(yōu)化傳輸內容的大小岔绸,減少服務端流量壓力。
Content-Encoding 的取值有 gzip
、defalte
捻激、compress
、identity
前计、br
等胞谭,具體用到哪個需要跟服務端協(xié)商好。Xcode 提供的 zlib 可以實現(xiàn)這里的 gzip
男杈、defalte
這兩種編碼格式丈屹。
gzip
一種由文件壓縮程序「Gzip,GUN zip」產(chǎn)生的編碼格式伶棒,描述于 RFC 1952旺垒。這種編碼格式是一種具有 32 位 CRC 的 Lempel-Ziv 編碼(LZ77);
deflate
由定義于 RFC 1950 的「ZLIB」編碼格式與 RFC 1951 中描述的「DEFLATE」壓縮機制組合而成的產(chǎn)物肤无;
由于 HTTP 1.1 的命名失誤先蒋,deflate
當初應該命名為 zlib
,因為其本身就是使用 zlib
格式編碼宛渐,用 deflate
造成了與 DEFLATE
算法的混淆竞漾,事實上 Content-Encoding
為 gzip
和 deflate
時都可能使用 DEFLATE
算法。
2. zlib
庫的使用
下面介紹一下使用 zlib
庫對 NSData 進行壓縮操作的基本過程以及注意事項:
2.1 引入頭文件
#import "zlib.h"
2.2 初始化
在開始使用 zlib.h
提供的方法壓縮數(shù)據(jù)之前皇忿,必需做一些初始化工作畴蹭。一般情況下初始化需要調用 deflateInit()
或者 deflateInit2()
函數(shù),用 deflateInit()
初始化后壓縮產(chǎn)生 zlib
編碼格式鳍烁,對應 Content-Encoding 中的 deflate
叨襟,而 deflateInit2()
通過不同參數(shù)配置可以產(chǎn)生對應 Content-Encoding 中的 gzip
和 deflate
的不同壓縮格式。
deflateInit2()
函數(shù)有很多個參數(shù)幔荒,第一個是個 C 結構體 z_stream
糊闽,定義在 zlib.h
中。
z_stream
這個結構體的成員用于控制壓縮算法的工作方式爹梁,同時也維護了兩組輸入輸出指針 next_in/out
右犹,還有關于已處理的字節(jié)數(shù)和剩余未處理的字節(jié)數(shù)等信息。
2.2.1 初始化 z_stream
z_stream
的初始化過程如下姚垃,其中 zalloc念链、zfree、opaque
設置為 NULL
這樣之后的 deflateInit2()
方法會把這些指針更新為默認的內存管理函數(shù)。
z_stream zStream;
bzero(&zStream, sizeof(zStream));
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
// 剩余的需要壓縮字指針
zStream.next_in = (Bytef *) data.bytes;
// 剩余的需要壓縮字節(jié)數(shù)
zStream.avail_in = (uInt) data.length;
// 目前已經(jīng)輸出的字節(jié)數(shù)
zStream.total_out = 0;
2.2.2 調用 deflateInit2()
先看一下 deflateInit2()
的定義
z_streamp strm: z_stream 指針掂墓;
int level: 壓縮等級谦纱,必需為 Z_DEFAULT_COMPRESSION 或者 0 ~ 9 的整數(shù),1為最快君编,9為最大限度壓縮跨嘉,0為不壓縮,數(shù)字越大越耗時吃嘿;
int method: 壓縮算法祠乃,只支持 Z_DEFLATED;
int windowBits: 歷史緩沖區(qū)最大尺寸兑燥,值為 2^windowBits, windowBits 的值為 8~15 時亮瓷,deflate() 方法生成 zlib 格式的數(shù)據(jù),當 windowBits 為 31 時 deflate() 方法生成 gzip 格式降瞳。當取值為 -15 ~ -8 時寺庄,deflate() 生成純 deflate 算法壓縮數(shù)據(jù)(不包含 zlib 和 gzip 格式頭和尾)
int strategy: 用于調整壓縮算法,一般使用 Z_DEFAULT_STRATEGY
ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
int level,
int method,
int windowBits,
int memLevel,
int strategy));
以生成 gzip
格式輸出為例:
OSStatus status = deflateInit2(&zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (status != Z_OK) {
return nil;
}
2.3 生成壓縮數(shù)據(jù)
完成初始化之后力崇,循環(huán)調用 deflate ()
方法不斷壓縮數(shù)據(jù)并填充到 NSMutableData
中,直到 deflate ()
返回結果不為 Z_BUF_ERROR
或者 Z_OK
為止赢织。
static NSInteger kZlibCompressChunkSize = 2048;
NSMutableData *compressedData = [NSMutableData dataWithLength:kZlibCompressChunkSize];
do {
if ((status == Z_BUF_ERROR) || (zStream.total_out == compressedData.length)) {
[compressedData increaseLengthBy:kZlibCompressChunkSize];
}
zStream.next_out = (Bytef *)compressedData.bytes + zStream.total_out;
zStream.avail_out = (uInt)(compressedData.length - zStream.total_out);
status = deflate(&zStream, Z_FINISH);
} while ((status == Z_BUF_ERROR) || (status == Z_OK));
status = deflateEnd(&zStream);
if ((status != Z_OK) && (status != Z_STREAM_END)) {
return nil;
}
compressedData.length = zStream.total_out;
3. 完成后的壓縮方法
- (NSData *)zlibCompressedData:(NSData *)data
{
if (data.length == 0) return data;
z_stream zStream;
bzero(&zStream, sizeof(zStream));
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
zStream.next_in = (Bytef *) data.bytes;
zStream.avail_in = (uInt) data.length;
zStream.total_out = 0;
OSStatus status = deflateInit2(&zStream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED, MAX_WBITS,
MAX_MEM_LEVEL,
Z_DEFAULT_STRATEGY);
if (status != Z_OK) {
return nil;
}
static NSInteger kZlibCompressChunkSize = 2048;
NSMutableData *compressedData = [NSMutableData dataWithLength:kZlibCompressChunkSize];
do {
if ((status == Z_BUF_ERROR) || (zStream.total_out == compressedData.length)) {
[compressedData increaseLengthBy:kZlibCompressChunkSize];
}
zStream.next_out = (Bytef *)compressedData.bytes + zStream.total_out;
zStream.avail_out = (uInt)(compressedData.length - zStream.total_out);
status = deflate(&zStream, Z_FINISH);
} while ((status == Z_BUF_ERROR) || (status == Z_OK));
status = deflateEnd(&zStream);
if ((status != Z_OK) && (status != Z_STREAM_END)) {
return nil;
}
compressedData.length = zStream.total_out;
return compressedData;
}