FFI 库,是 LuaJIT 中最重要的一个扩展库。它允许从纯 Lua 代码调用外部 C 函数,使用 C 数据结构。
zlib压缩数据的demo
// encode
const char* src = "test zlib";
uLong srcLen = strlen(src) + 1;
uLong dstLen = 255;
Bytef dst[255] = { 0 };
int res = compress(dst, &dstLen, (const Bytef*)src, srcLen);
CCLOG("compress data addr: %p", dst); // zlib数据是以78 9c开头
// decode
uLong decodeLen = 255;
char decode[255] = { 0 };
res = uncompress((Bytef*)decode, &decodeLen, dst, dstLen);
CCLOG("decode");
但是我看到ffi-zlib使用的是
int inflate(z_stream*, int flush);
int inflateEnd(z_stream*);
int inflateInit2_(z_stream*, int windowBits, const char* version, int stream_size);
int deflate(z_stream*, int flush);
int deflateEnd(z_stream* );
int deflateInit2_(z_stream*, int level, int method, int windowBits, int memLevel,int strategy, const char *version, int stream_size);
deflate/inflate提供一套基于stream流的操作接口,需要使用者管理较多的状态,但可以实现过程中暂停/恢复操作;
compress/uncompress提供一套基于block块的操作接口,对deflate/inflate的封装,需要用户确保足够内存,一次调用完成操作.
uncompress将内存中数据进行解压,与compress函数一起使用,实现过程中会调用inflate函数,而且需要对inflate函数中的流参数进行初始化。
inflate函数是uncompress实现的一部分,提现的是deflate压缩思想,但是不能直接使用,需要很多参数的配置。
一些概念
- deflate(RFC1951):一种压缩算法,使用LZ77和哈弗曼进行编码;
- zlib(RFC1950):一种格式,是对deflate进行了简单的封装
- gzip(RFC1952):一种格式,也是对deflate进行的封装。
Deflate算法
Deflate 是一种用于数据压缩的算法,它广泛应用于各种领域和技术中。
Deflate 算法基于哈夫曼编码和 LZ77 算法,通过消除数据中的冗余和重复信息来实现高效的压缩。
Deflate 算法的主要步骤包括:
- 压缩:原始数据被分为多个小块,并使用 LZ77 算法找到每个块中的重复片段。这些重复片段由一个指针和长度表示。然后,通过哈夫曼编码将指针和长度进行编码,以减少其表示所需的位数。最终,所有编码的块和额外的压缩信息被组合成一个压缩的数据流。
- 解压缩:解压缩过程与压缩过程相反。首先,被压缩的数据流被解码为压缩块和其他压缩信息。然后,使用哈夫曼解码器解码指针和长度,恢复出重复片段。最后,根据得到的重复片段,使用 LZ77 解压算法重新构建原始数据。
Deflate 算法在很多应用中都有广泛的应用,包括网页传输中的 HTTP 响应压缩、文件压缩格式(如 ZIP)、PNG 图像文件的压缩等。
local function createStream(bufsize)
-- Setup Stream
-- z_stream是C层定义的结构体
-- ffi.new 是用于创建 C 数据类型实例的函数
local stream = ffi_new("z_stream")
-- Create input buffer var
-- ?是占位符,代表数组的大小,+1是为了留出末尾的空字符
local inbuf = ffi_new('char[?]', bufsize+1)
-- ↓待压缩的数据 ↓待压缩数据的长度
stream.next_in, stream.avail_in = inbuf, 0
-- create the output buffer
local outbuf = ffi_new('char[?]', bufsize)
-- ↓压缩后的数据 ↓压缩后的长度
stream.next_out, stream.avail_out = outbuf, 0
return stream, inbuf, outbuf
end
local function initDeflate(stream, options)
-- Setup deflate process
local method = zlib.Z_DEFLATED
local level = options.level or zlib.Z_DEFAULT_COMPRESSION
local memLevel = options.memLevel or 8
local strategy = options.strategy or zlib.Z_DEFAULT_STRATEGY
local windowBits = options.windowBits or (15 + 16) -- +16 sets gzip wrapper not zlib
local version = ffi_str(zlib.zlibVersion())
-- 加载zlib库
local zlib = ffi.load(ffi.os == "Windows" and "zlib1" or "z")
-- 如果成功则返回 `Z_OK`
-- 如果没有足够的内存,则返回 `Z_MEM_ERROR`
-- 如果参数无效(例如无效的方法),则返回 `Z_STREAM_ERROR`
-- 如果 zlib 库版本 ( `zlib_version` ) 与调用方假定的版本 ( `ZLIB_VERSION` ) 不兼容,则返回 `Z_VERSION_ERROR` 。
return zlib.deflateInit2_(
stream, -- 压缩上下文
level, -- 压缩等级
method, -- 压缩方法
windowBits, -- 窗口比特数,此参数的值越大,压缩效果越好,但会牺牲内存使用量
memLevel, -- 压缩过程中对内存的限制
strategy, -- 调整优化压缩算法的细节
version,
ffi_sizeof(stream)
)
end
local DEFAULT_CHUNK = 16384
function _M.deflateGzip(input, output, bufsize, options)
local bufsize = bufsize or DEFAULT_CHUNK
options = options or {}
-- Takes 2 functions that provide plain input data and receives output data
-- Returns gzip compressed string
-- 创建数据流
local stream, inbuf, outbuf = createStream(bufsize)
-- 初始化deflate
local init = initDeflate(stream, options)
if init == Z_OK then
-- 开始真正的压缩
return deflate(input, output, bufsize, stream, inbuf, outbuf)
else
-- Init error 释放stream申请的内存
zlib.deflateEnd(stream)
return false, "INIT: "..zlib_err(init)
end
end
之前的都是准备工作,真正的压缩逻辑在这里
local function deflate(input, output, bufsize, stream, inbuf, outbuf)
local zlib_flate = zlib.deflate -- zlib的压缩函数
local zlib_flateEnd = zlib.deflateEnd
-- Deflate a stream
local err = 0
local mode = Z_NO_FLUSH
repeat
-- Read some input
local data = input(bufsize)
if data ~= nil then
-- copy内存数据,将data复制到inbuf
ffi_copy(inbuf, data)
-- 更新stream的输入数据、源数据长度
stream.next_in, stream.avail_in = inbuf, #data
else
-- EOF, try and finish up
mode = Z_FINISH
stream.avail_in = 0
end
-- While the output buffer is being filled completely just keep going
repeat
stream.next_out = outbuf
stream.avail_out = bufsize
-- Process the stream
-- mode:通常参数 `flush` 设置为 `Z_NO_FLUSH` ,决定在产生输出之前要积累多少数据,以最大化压缩。
err = zlib_flate(stream, mode)
-- Only possible *bad* return value here
if err == Z_STREAM_ERR then
-- Error, clean up and return
zlib_flateEnd(stream)
return false, "DEFLATE: "..zlib_err(err), stream
end
-- Write the data out:将数据输出到output
local err = flushOutput(stream, bufsize, output, outbuf)
if err then
zlib_flateEnd(stream)
return false, "DEFLATE: "..err
end
until stream.avail_out ~= 0
-- In deflate mode all input must be used by this point
if stream.avail_in ~= 0 then
zlib_flateEnd(stream)
return false, "DEFLATE: Input not used"
end
until err == Z_STREAM_END
-- Stream finished, clean up and return
zlib_flateEnd(stream)
return true, zlib_err(err)
end
local function flushOutput(stream, bufsize, output, outbuf)
-- Calculate available output bytes
local out_sz = bufsize - stream.avail_out
local ss = 0
local ss2 = stream.total_in
local ss3 = stream.total_out
if out_sz == 0 then
return
end
-- Read bytes from output buffer and pass to output function
-- output是外边传如的function,ffi_str 将 C 字符串或字节数组转换为 Lua 字符串
local ok, err = output(ffi_str(outbuf, out_sz))
if not ok then
return err
end
end
测试例子
data: 12345671234567
len: 20
1F 8B 08 00 00 00 00 00 00 0B 03 00 00 00 00 00 00 00 00 00
c++的结果是len=18
原因是在input的逻辑里面不要操作数据源。
遇到的新问题,上传到腾讯云后,再下载后无法解压,提示数据错误,估计是下载导致的