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的逻辑里面不要操作数据源。
遇到的新问题,上传到腾讯云后,再下载后无法解压,提示数据错误,估计是下载导致的