zlib inflate()解压缓冲区时部分挂起问题求助
我在MicroChip/XC32平台使用zlib 1.3.1(通过Harmony导入,编译正常),目标是通过串口接收数据,未压缩数据传输已正常工作。为节省传输时间,我测试对4k、8k及16k大小的二进制数据块压缩后循环传输,在单片机端调用inflate()解压。
测试阶段使用Python 3.6的zlib 1.2.10进行压缩,代码如下:
# 创建自定义配置的压缩器 compressor = zlib.compressobj( level=zlib.Z_BEST_COMPRESSION, wbits=zlib.MAX_WBITS ) data = compressor.compress(datablock) + compressor.flush() # datablock大小为16k # 发送数据...
单片机接收数据后,在rcvbuf[]中存储数据,处理代码如下:
#include "zlib.h" uint8_t rcv_buf[LEN_RCV_BUF]; // LEN_RCV_BUF = 17000 //uint8_t decompress_buf[LEN_RCV_BUF]; uint8_t decompress_buf[40000]; // 测试时已扩容 uint8_t my_adr[40000]; // 测试时已扩容 /* * 调用此函数时,rcv_buf已填充完成 */ void cmd_write_to_myadr(void) { volatile z_stream out_stream; // zlib结构体 uint16_t have; int ret; uint16_t dat_len = ( rcv_buf[5] << 8 ) + rcv_buf[6]; /* 初始化解压状态 */ out_stream.zalloc = Z_NULL; out_stream.zfree = Z_NULL; out_stream.opaque = Z_NULL; out_stream.avail_in = 0; out_stream.next_in = Z_NULL; ret = inflateInit( &out_stream ); // HEAP-Size if( ret != Z_OK ) { // return ret; // neg_response(); // AAA return; } // rcv_buf[7] != 0 表示数据已压缩 out_stream.avail_in = dat_len; out_stream.next_in = &rcv_buf[8]; out_stream.avail_out = sizeof( decompress_buf ); out_stream.next_out = &decompress_buf[0]; // 执行解压 ret = inflate( &out_stream, Z_NO_FLUSH ); assert(ret != Z_STREAM_ERROR); /* 确保状态未被破坏 */ switch( ret ) { case Z_NEED_DICT : ret = Z_DATA_ERROR; /* 继续执行 */ case Z_DATA_ERROR : case Z_MEM_ERROR : inflateEnd( &out_stream ); // return ret; // negativ_response(); return; // 断点触发位置!!! case Z_STREAM_END : break; default: return; // 断点触发位置!!! } have = sizeof( decompress_buf ) - out_stream.avail_out; memWrite_sram_buffer( my_adr, &decompress_buf[0], have ); my_adr += have; inflateEnd( &out_stream ); }
我传输的是图像和代码等二进制数据,问题在于:根据压缩数据块内容不同,解压有时正常有时失败。尝试过不同选项和窗口大小,但多次调用inflate()后总会出现问题,断点显示函数在内部挂起。目前Python仅用于测试,后续会替换为C++代码。请问是否是压缩和解压使用不同版本zlib导致的?
zlib的版本兼容性设计得很好,1.2.10和1.3.1之间不会出现兼容性问题——zlib的压缩格式是向后兼容的,新版本的inflate完全可以处理旧版本compress生成的数据,反之亦然。你的问题大概率不是版本差异导致的,建议从以下几个方向排查:
串口传输的完整性问题
二进制数据在串口传输时容易出现丢字节、错序的情况,尤其是循环传输多块数据时。可以在每块压缩数据前加上校验码(比如CRC32),单片机接收后先校验数据完整性,再进行解压。
另外要检查dat_len的计算是否正确:(rcv_buf[5] << 8) + rcv_buf[6]是否和Python端发送的压缩数据长度完全一致?如果长度计算错误,inflate会读到不完整或多余的数据,直接导致解压失败。解压逻辑的缺陷
你当前只调用了一次inflate,但inflate不一定能一次处理完所有输入数据。正确的做法应该是循环调用inflate,直到返回Z_STREAM_END或者错误:do { ret = inflate(&out_stream, Z_NO_FLUSH); assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&out_stream); return; } have = sizeof(decompress_buf) - out_stream.avail_out; // 处理已解压的数据 memWrite_sram_buffer(my_adr, decompress_buf, have); my_adr += have; // 重置输出缓冲区,准备接收下一批解压数据 out_stream.next_out = decompress_buf; out_stream.avail_out = sizeof(decompress_buf); } while (out_stream.avail_in > 0);另外,
volatile修饰z_stream可能会干扰zlib内部的状态更新,建议去掉volatile——zlib的操作是单线程的,不需要这个修饰符。内存问题
单片机的堆空间可能不足,inflateInit需要分配一定的内存。要确认inflateInit的返回值确实是Z_OK,有时候堆空间不足会导致初始化失败,但你的代码只是直接返回,没有任何报错提示,容易忽略这个问题。
还要检查decompress_buf的内存是否足够,虽然你已经扩容到40k,但如果某些数据块的压缩比极低(比如本身就是高压缩率的二进制数据),解压后的大小可能超过40k,导致缓冲区溢出,破坏zlib的状态结构。Python压缩的细节
使用compressor.flush()默认会生成Z_FINISH的结束标记,这没问题,但如果是循环传输多个数据块,要确保每个数据块都是独立的压缩流——不要复用同一个compressobj处理多个数据块,每个数据块都创建新的compressobj,避免流状态互相干扰。
内容的提问来源于stack exchange,提问作者Craggan

