zlib 膨胀流和 avail_in

zlib inflate stream and avail_in

我正在处理的应用程序的一部分涉及通过套接字逐段接收 zlib (deflate) 格式的压缩数据流。该例程基本上是接收块中的压缩数据,并在更多数据可用时将其传递给 inflate。当 inflate returns Z_STREAM_END 我们知道完整的对象已经到达。

基本C++ inflater函数的一个非常简化的版本如下:

void inflater::inflate_next_chunk(void* chunk, std::size_t size)
{
   m_strm.avail_in = size;
   m_strm.next_in = chunk;
   m_strm.next_out = m_buffer;

   int ret = inflate(&m_strm, Z_NO_FLUSH);
   /* ... check errors, etc. ... */
}

奇怪的是,每次点赞... 40 次左右,inflate 都会失败并显示 Z_DATA_ERROR

根据 zlib manualZ_DATA_ERROR 表示 "corrupt or incomplete" 流。显然,在我的应用程序中,数据可能会被破坏的方式有很多种,这超出了这个问题的范围——但经过一番修改后,我意识到对 inflate 的调用会 return Z_DATA_ERROR 如果 m_strm.avail_in 在我将其设置为 size 之前 而不是 0。换句话说,似乎 inflate 失败了,因为在我设置 avail_in 之前,流 中已经有 数据。

但我的理解是每次调用 inflate 都应该完全清空输入流,这意味着当我再次调用 inflate 时,如果它没有完成我不必担心最后一次通话。我的理解在这里正确吗?或者我是否总是需要检查 strm.avail_in 以查看是否有待处理的输入?

此外,为什么会有待处理的输入?为什么 inflate 不在每次调用时简单地消耗所有可用输入?

inflate() 可以 return 因为它已经填满了输出缓冲区但没有消耗所有的输入数据。如果发生这种情况,您需要提供一个新的输出缓冲区并再次调用 inflate() 直到 m_strm.avail.in == 0.

zlib 手册是这样说的...

The detailed semantics are as follows. inflate performs one or both of the following actions:

Decompress more input starting at next_in and update next_in and avail_in accordingly. If not all input can be processed (because there is not enough room in the output buffer), next_in is updated and processing will resume at this point for the next call of inflate().

您似乎假设您的压缩输入将始终适合您的输出缓冲区space,但情况并非总是如此...

我的包装器代码如下所示...

bool CDataInflator::Inflate(
   const BYTE * const pDataIn,
   DWORD &dataInSize,
   BYTE *pDataOut,
   DWORD &dataOutSize)
{
   if (pDataIn)
   {
      if (m_stream.avail_in == 0)
      {
         m_stream.avail_in = dataInSize;
         m_stream.next_in = const_cast<BYTE * const>(pDataIn);
      }
      else
      {
         throw CException(
            _T("CDataInflator::Inflate()"),
            _T("No space for input data"));
      }
   }

   m_stream.avail_out = dataOutSize;
   m_stream.next_out = pDataOut;

   bool done = false;

   do
   {
      int result = inflate(&m_stream, Z_BLOCK);

      if (result < 0)
      {
         ThrowOnFailure(_T("CDataInflator::Inflate()"), result);
      }

      done = (m_stream.avail_in == 0 || 
             (dataOutSize != m_stream.avail_out &&
              m_stream.avail_out != 0));
   }
   while (!done && m_stream.avail_out == dataOutSize);

   dataInSize = m_stream.avail_in;

   dataOutSize = dataOutSize - m_stream.avail_out;

   return done;
}

注意循环以及调用者依赖 dataInSize 来了解所有当前输入数据何时被消耗的事实。如果输出 space 已满,则调用者使用 Inflate(0, 0, pNewBuffer, newBufferSize); 再次调用以提供更多缓冲区 space...

考虑将 inflate() 调用包装在 do-while 循环中,直到流的 avail_out 不为空(即已提取一些数据):

m_strm.avail_in = fread(compressed_data_buffer, 1, some_chunk_size / 8, some_file_pointer);
m_strm.next_in = compressed_data_buffer;
do {
   m_strm.avail_out = some_chunk_size;
   m_strm.next_out = inflated_data_buffer;
   int ret = inflate(&m_strm, Z_NO_FLUSH);
   /* error checking... */
} while (m_strm.avail_out == 0);
inflated_bytes = some_chunk_size - m_strm.avail_out;

如果不调试 inflate() 的内部工作,我怀疑它有时可能只需要 运行 多次就可以提取可用数据。