Программирование PC Пример использования библиотеки zlib Tue, November 20 2018  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Пример использования библиотеки zlib Печать
Добавил(а) microsin   

Часто задают вопросы по использованию функций deflate() и inflate(). Пользователи интересуются, когда нужно больше предоставить данных на входе, когда нужно использовать больше данных на выходе, что делать при ошибке Z_BUF_ERROR, как правильно завершать процесс, и так далее. Для тех пользователей, кто хотя бы один раз (или лучше несколько раз) ознакомился с содержимым файла zlib.h, указания ниже будут дополнительной информацией по примерам программ на языке C, где применяются простые подпрограммы для запаковки и распаковки файлов с помощью функций deflate() и inflate() соответственно. Мы надеемся, что это поможет Вам разобраться в библиотеке zlib (перевод документации файла zlib-1.2.11\examples\zlib_how.html [1]).

Давайте рассмотрим код из программы zpipe.c:

/* zpipe.c: пример правильного использования zlib-функций inflate() и deflate()
   Не накладываются правовые ограничения -- предоставлено для общего пользования
   Version 1.4  11 December 2005  Mark Adler */
 
/* История версий:
   1.0  30 Oct 2004  Первая версия
   1.1   8 Nov 2004  Добавлено приведение типа void для не используемых значений возврата.
                     Используйте оператор switch для разбора возвращаемых значений
                     функции inflate().
   1.2   9 Nov 2004  Добавлены утверждения assert для документации zlib.
   1.3   6 Apr 2005  Удален некорректный assertion в inf().
   1.4  11 Dec 2005  Добавлен хак для обхода преобразований конца строки MSDOS.
                     Убраны некоторые предупреждения компилятора для входных
                      и выходных буферов.
*/

Далее добавлено подключение требуемых заголовков. Из stdio.h используются fopen(), fread(), fwrite(), feof(), ferror() и fclose() для файлового ввода/вывода, и функция fputs() используется для сообщений об ошибках. Из string.h используется strcmp() для обработки аргументов командной строки. Из assert.h мы используем макрос assert(). Из zlib.h используются базовые функции сжатия deflateInit(), deflate() и deflateEnd(), и базовые функции распаковки inflateInit(), inflate() и inflateEnd().

#include < stdio.h >
#include < string.h >
#include < assert.h >
#include "zlib.h"

Следующий код с условными операторами препроцессора позволяет избежать порчи входных и выходных данных на операционных системах Windows/MS-DOS. Он добавляет макрос для переключения файлового ввода/вывода в двоичный режим. Иначе системы Windows/MS-DOS предполагают, что входные и выходные файлы текстовые, и пытаются преобразовать символы конца строки из одного стандарта в другой. Такое поведение повреждает двоичные данные, и в результате сжатые данные нельзя использовать. Чтобы устранить преобразование конца строк, файловый ввод и вывод переключается в двоичный режим. Определяемый макрос SET_BINARY_MODE() будет использоваться в начале функции main().

#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
#  include < fcntl.h >
#  include < io.h >
#  define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
#else
#  define SET_BINARY_MODE(file)
#endif

Константа CHUNK просто задает размер буфера для передачи и получения данных в функции zlib и из них. Чем больше размер буферов, тем работа будет эффективнее, особенно для алгоритма inflate(). Если доступна лишняя память, то размеры буферов должны быть выбраны порядка 128K или 256K.

#define CHUNK 16384

[Компрессия данных]

Функция def() сжимает данные входного файла и помещает сжатые данные в выходной файл. Выходные данные будут в формате zlib, который отличается от форматов gzip или zip. У формата zlib очень маленький заголовок, в котором только 2 байта для идентификации формата (что это поток zlib), и предоставления информации для декодирования, и 4-байтный хвост со значением для быстрой проверки - это позволяет проверить целостность распакованных данных после декодирования.

/* Сжатие входного файла source и запись сжатых данных в выходной файл dest,
   пока обработка не дойдет до конца EOF исходного файла source. Функция
   def() вернет Z_OK в случае успеха, Z_MEM_ERROR если не может быть
   выделена память для обработки, Z_STREAM_ERROR если задан недопустимый
   уровень сжатия level, Z_VERSION_ERROR если версия zlib.h и версия
   линкованной библиотеки не соответствуют друг другу, или Z_ERRNO если
   произошла ошибка чтения или записи файлов. */
int def(FILE *source, FILE *dest, int level)
{

Здесь определены локальные переменные для def(). Переменная ret будет использоваться для кодов возврата zlib. Переменная flush отслеживает текущее состояние для функции deflate(), которое может быть no flush или flush для завершения после того, как был достигнут конец входного файла. Переменная have предназначена для хранения количества данных, возвращаемых из deflate(). Структура strm используется для передачи и функции zlib и из них, и поддержки состояния deflate(). Переменные in и out это входные и выходные буферы для deflate().

   int ret, flush;
   unsigned have;
   z_stream strm;
   unsigned char in[CHUNK];
   unsigned char out[CHUNK];

Первое, что надо сделать - инициализировать состояние zlib для компрессии, что делается вызовом deflateInit(). Это должно быть выполнено перед первым использованием функции deflate(). Поля zalloc, zfree и opaque в структуре strm должны быть инициализированы перед вызовом deflateInit(). Здесь они установлены в константу Z_NULL, что указывает библиотеке zlib использовать память по умолчанию для подпрограмм выделения памяти. В приложении здесь могут так же быть предоставлены пользовательские утилиты для предоставления памяти. Функция deflateInit() выделит буфер порядка 256K для внутреннего состояния (подробности см. в техническом руководстве zlib [2]).

Функция deflateInit() вызывается с указателем на структуру, которая будет инициализирована уровнем сжатия level. Это целое число в диапазоне от -1 до 9. Чем меньше число, тем ниже уровень компрессии, в результате сжатие будет происходить быстрее, но размер выходных данных будет больше. Более высокие уровни дают лучшее сжатие, но процесс идет медленнее. Константа zlib Z_DEFAULT_COMPRESSION, равная -1, задает хороший компромисс между сжатием и скоростью, что эквивалентно уровню 6. Уровень 0 в действительности не дает никакого сжатия, и фактически генерирует выходные данные в формате zlib (это не побайтная копия со входа на выход). Более продвинутые приложения могут использовать deflateInit2() вместо deflateInit(). Эти приложения тогда смогут снизить количество используемой памяти ценой некоторого ухудшения сжатия. Также функция deflateInit2() может понадобиться для запроса создания заголовка и хвоста gzip вместо заголовка и хвоста zlib, или для простого кодирования без использования заголовка и хвоста.

Мы должны проверить значение, возвращаемое из deflateInit(), на равенство константе Z_OK, чтобы убедиться, что успешно выделена память для внутреннего состояния, и что предоставленные аргументы имеют допустимые значения. Функция deflateInit() также проверит, что версия zlib, указанная в файле zlib.h, соответствует версии zlib, реально линкованной с программой. Это особенно важно для рабочих окружений, где zlib используется как общая библиотека.

   /* Инициализация состояния deflate */
   strm.zalloc = Z_NULL;
   strm.zfree = Z_NULL;
   strm.opaque = Z_NULL;
   ret = deflateInit(&strm, level);
   if (ret != Z_OK)
      return ret;

Обратите внимание, что приложение может использовать несколько независимых потоков zlib, которые могут работать параллельно. Информация о состоянии, содержащаяся в структуре, позволяют подпрограммам zlib быть реентрантными.

reentrant, реентрантная функция термин относится к многозадачной среде выполнения, и применяется к функциям и подпрограммам. Функция или подпрограмма считается реентрантной, если она написана так, что если выполнение функции будет остановлено в любом месте (например, при прерывании или переключении контекста), и функция будет вызвана заново (сначала) уже в новом контексте, то это никак не повлияет на успешную работу функции. Т. е. функция всегда успешно завершит свою работу, будучи одновременно запущенной и/или приостановленной в разных контекстах. К реентерабельным нельзя отнести такие функции и подпрограммы, которые обращаются к глобальным переменным или имеют в своем теле статические переменные.

С шутками/прибаутками теперь можно приступать к делу. Внешний цикл do читает данные из входного файла, и выходит из него при достижении конца файла. Этот цикл содержит только вызов функции deflate(). Таким образом мы должны удостовериться, что всех входные данные были обработаны, и выходные данные были сгенерированы перед тем, как вывалиться из цикла в нижнюю часть функции def.

   /* Сжатие до конца файла */
   do {

Обработка начинается с чтения данных из входного файла. Количество прочитанных данных помещается непосредственно в avail_in, и указатель на эти байты помещается в next_in. Также вызовом feof мы проверяем, достигнут ли конец файла. Если конец файла достигнут, то flush установится в константу Z_FINISH, которая позже передается в deflate(), чтобы показать, что это последний кусок входных данных для сжатия. Функция feof() нужна для проверки на конец файла, и чтобы узнать, что было прочитано меньше CHUNK байт. Причина в том, что если длина входного файла нацело делится на CHUNK, то мы пропустим событие конца файла, и не узнаем, что надо сказать для deflate() завершить поток сжатия. Если мы находимся пока еще не в конце ввода, то константа Z_NO_FLUSH будет передана в deflate, чтобы показать, что мы еще находимся в процессе получения несжатых данных.

Если здесь произойдет ошибка чтения входного файла, то процесс оборвется, и перед возвратом ошибки будет вызвана функция deflateEnd(), чтобы освободить память, выделенную под состояние zlib. Мы ведь не хотим получить утечку памяти, правильно? Функция deflateEnd() может быть вызвана в любой момент после того, как состояние zlib было инициализировано. После этого для начала нового процесса сжатия снова должна быть вызвана deflateInit() (или deflateInit2()). Нет смысла проверять код возврата из deflateEnd(), освобождение памяти не может потерпеть неудачу.

      strm.avail_in = fread(in, 1, CHUNK, source);
      if (ferror(source)) {
         (void)deflateEnd(&strm);
         return Z_ERRNO;
      }
      flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
      strm.next_in = in;

Внутренний цикл do передает наш кусочек входных данных в функцию deflate(), и вызовы deflate() продолжаются до конца входных данных, и при этом формируются выходные данные. Как только нет больше новых выходных данных, deflate() гарантирует, что обработаны все входные данные, т. е. avail_in будет равна 0.

      /* Запуск deflate() для обработки входных данных, пока не заполнится
         выходной буфер, с завершением сжатия, когда все данные источника
         будут прочитаны */
      do {

Пространство, предоставляемое для выходных данных deflate(), определяется установкой переменной avail_out в количество доступных выходных байт, и next_out указывает на это место.

         strm.avail_out = CHUNK;
         strm.next_out = out;

Теперь мы вызываем код сжатия в функции deflate(). Она берет столько байт avail_in, сколько может обработать, и записывает avail_out байт в место по указателю next_out. Тогда эти счетчики и указатели обновляются после того, как обработаны входные данные и записаны выходные данные. Величина доступного места под выходные данные может ограничить количество входных данных, которое может быть обработано. Следовательно внутренний цикл гарантирует, что всех входные данные обработаны, предоставляя каждый раз больше места под выходные данные. Поскольку avail_in и next_in обновляются функцией deflate(), мы не должны терять их значения между вызовами deflate(), пока не будут обработаны все данные.

Параметрами для deflate() являются указатель на структуру strm, где содержится информация о входных и выходных данных вместе с внутренним текущим состоянием алгоритма сжатия, и параметр flush, показывающий необходимость сбрасывать данные на выходе. Обычно deflate заглатывает несколько килобайт входных данные перед тем, как что-либо выведет (это не касается генерации заголовка выходного файла), чтобы накапливать статистику по данным в целях оптимальной компрессии данных. И она будет выводить наружу пакет сжатых данных, и продолжать обрабатывать входные данные до момента следующего сброса выходных данных. Когда-нибудь функции deflate() нужно сказать завершить поток компрессии на предоставленных входных данных, и записать хвост выходного файла с проверочным значением. Функция deflate() будет продолжать процесс сжатия, пока параметр flush равен Z_NO_FLUSH. Как только предоставлен параметр Z_FINISH, функция deflate() завершит формирование сжатого выходного потока. Однако в зависимости от того, сколько предоставлено места под выходные данные, deflate() может быть вызвана несколько раз для завершения потока сжатых данных, даже после того, как были переданы на вход все данные. Параметр flush должен оставаться в значении Z_FINISH для этих последующих вызовов.

В более продвинутых приложениях параметр flush может принимать другие значения. Вы можете заставить deflate() генерировать выходные данные для всех поступивших до настоящего момента, даже если обычный процесс сжатия этого пока не требовал бы (такое поведение может быть необходимо, например, для управления задержкой данных на линии связи со сжатыми данными). Также Вы можете запросить у deflate() стирать любую историю статистики сжатия, чтобы все, что последует дальше, было запаковано независимо (например, это может понадобится для приложений со произвольным доступом к содержимому архива). Оба этих запроса ухудшают компрессию на величину, зависящую от того, насколько часто делаются такие запросы.

Функция deflate() возвращает значение, которое может сообщать об ошибках, хотя в этом примере мы эти коды возврата не проверяем. Почему? Потому что в данном случае deflate() не может столкнуться с ошибкой. Давайте рассмотрим, какие значения может вернуть deflate(). Возможные значения Z_OK, Z_STREAM_END, Z_STREAM_ERROR или Z_BUF_ERROR. Z_OK означает, что все в порядке. Z_STREAM_END это также нормальный код возврата, он будет возвращен на последнем вызове deflate(). Это гарантирует, что вызовы deflate() с параметром Z_FINISH больше не будут генерировать выходных данных, сигнализируя о завершении работы. Значение Z_STREAM_ERROR может быть возвращено только если поток сжатия не был правильно инициализирован, но мы гарантированно его правильно инициализировали. Так что здесь нет смысла проверять значение возврата на Z_STREAM_ERROR, например чтобы проверить возможность неожиданной порчи памяти состояния zlib другой частью приложения. Значение Z_BUF_ERROR будет объяснено позже, но достаточно заметить, что это просто индикация, что deflate() не может больше потреблять входные данные и не может ничего больше поставлять на выход. С этим кодом функцию deflate() можно вызывать снова и снова с предоставлением большего места под выходные данные ли большего количества доступных входных данных.

         ret = deflate(&strm, flush);    /* нет плохого значения возврата */
         assert(ret != Z_STREAM_ERROR);  /* этот assert гарантирует, что состояние zlib не испорчено */

Теперь мы вычислим, сколько место под выходные данные было предоставлено deflate() на последнем вызове, которое будет разницей между тем, сколько места было перед вызовом, и сколько осталось места после вызова. Тогда эти данные, если они есть, записываются в выходной файл. Тогда мы можем использовать выходной буфер повторно для следующего вызова deflate(). И снова, если в этом месте произошла ошибка файлового ввода/вывода, то перед выходом мы вызовем deflateEnd(), чтобы избежать утечки памяти.

         have = CHUNK - strm.avail_out;
         if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
            (void)deflateEnd(&strm);
            return Z_ERRNO;
         }

Внутренний цикл do повторяется, пока последний вызов deflate() даст отказ из-за невозможности заполнения выходного буфера. Тогда мы знаем, что deflate() обработала входные данные входного буфера, мы можем вывалиться из этого цикла и повторно использовать входной буфер.

Способ, которым функция deflate() говорит о том, что не заполняет выходной буфер, состоит в том, что она оставляет переменную avail_out в значении больше 0. Однако предположим, что у deflate() нет больше данных на выходе, но оказалось, что буфер все-таки заполнен полностью! Тогда avail_out станет равной нулю, и мы не сможем сказать, что deflate() сделала все, что может. Насколько мы знаем, у deflate() есть больше выходных данных для нас, так что вызовем её снова. Но теперь deflate() не передаст на выход никаких данных, и avail_out останется не измененной как CHUNK. Вызов deflate() не может делать ничего, ни больше выводить выходные данные, ни потреблять входные данные, и тогда она вернет Z_BUF_ERROR. Однако это не сигнализирует от проблеме, просто это нужная для нас индикация, что следует вывалиться из внутреннего цикла и предоставить для deflate() больше входных данных.

С параметром flush, установленным в значение Z_FINISH, наступает последний набор вызовов deflate(), с которыми будет завершаться поток выходных данных. Как только такой вызов сделан, последующие вызовы deflate() возвратят Z_STREAM_ERROR, если вдруг параметр flush окажется не равным Z_FINISH, и дальнейшая обработка станет невозможной до повторной инициализации состояния zlib.

У некоторых приложений zlib есть 2 цикла, которые вызывают deflate() вместо одного внутреннего цикла, как у нас. Первый цикл делает вызовы без выполнения сброса выходных данных (параметр flush = Z_NO_FLUSH), и передает все входные данные в deflate(). Второй цикл делает вызовы deflate() с параметром flush = Z_FINISH для завершения процесса. Как можно увидеть из этого примера, такого поведения можно избежать простым отслеживанием текущего состояния flush.

      } while (strm.avail_out == 0);
      assert(strm.avail_in == 0);     /* будут использоваться все входные данные */

Теперь мы делаем проверку, обработали ли мы весь входной файл. Информация об этом была сохранена в переменной flush, так что мы увидим, что она была установлена в Z_FINISH. Если это так, то произойдет выход из внешнего цикла. Мы гарантировали получение Z_STREAM_END из последнего вызова deflate(), так что мы можем запускать её, пока последний кусок входных данных не будет обработан и все выходные данные не будут сгенерированы.

      /* Завершение, когда были обработаны последние данные файла */
   } while (flush != Z_FINISH);
   assert(ret == Z_STREAM_END);        /* Поток будет завершен */

Процесс сжатия завершен, но нам все еще нужно выполнить освобождение выделенной под состояние zlib память, чтобы избежать утечки памяти.

   /* Очистка и возврат из функции def() */
   (void)deflateEnd(&strm);
   return Z_OK;
}

[Декомпрессия данных]

Теперь мы делаем нечто подобное для распаковки данных в функции inf(). Функция inf() делает декомпрессию правильно сформированного потока zlib, полученного из входного файла source, записывая распакованные данные в выходной файл dest. Многое из обсуждения функции def() также применимо и для функции inf(), поэтому здесь будут рассмотрены только отличия между этими двумя функциями.

/* Декомпрессия из файла source в файл dest, пока поток данных не закончится на EOF.
   Функция inf() вернет Z_OK в случае успеха, Z_MEM_ERROR если не может быть
   выделена память для обработки, Z_DATA_ERROR если в сжатых данных есть ошибка
   или они неполные, Z_VERSION_ERROR если версия zlib.h и версия линкованной
   библиотеки не соответствуют друг другу, или Z_ERRNO если произошла ошибка
   при чтении или записи файлов. */
int inf(FILE *source, FILE *dest)
{

Локальные переменные inf() предназначены для тех же целей, что и у def(). Различие только в том, что здесь нет переменной flush, поскольку inflate() сама может определить из потока zlib, когда сжатые данные завершились.

   int ret;
   unsigned have;
   z_stream strm;
   unsigned char in[CHUNK];
   unsigned char out[CHUNK];

Инициализация состояния делается точно также, за исключением того, что не указывается уровень сжатия, и инициализируются еще 2 элемента структуры. Переменные avail_in и next_in должны быть инициализированы перед вызовом inflateInit(). Причина в том, что приложение имеет опцию для предоставления начала потока zlib, чтобы inflateInit() имела доступ к информации о методе сжатия, что помогает при выделении памяти. В текущей реализации zlib (через версии 1.2.x) все равно так или иначе задерживаются выделения памяти, зависящие от метода выделения, до первого вызова inflate(). Однако эти поля должны быть инициализированы, поскольку последующие версии zlib, предоставляющие больше методов сжатия, могут получить преимущество от такого интерфейса. В любом случае функция inflateInit() не делает никакой декомпрессии, так что поля avail_out и next_out не нужно инициализировать перед вызовом inflateInit().

Здесь avail_in устанавливается в 0, и next_in устанавливается в Z_NULL, чтобы показать, входные данные не были предоставлены.

   /* Выделение памяти под состояние zlib и инициализация этого состояния */
   strm.zalloc = Z_NULL;
   strm.zfree = Z_NULL;
   strm.opaque = Z_NULL;
   strm.avail_in = 0;
   strm.next_in = Z_NULL;
   ret = inflateInit(&strm);
   if (ret != Z_OK)
      return ret;

Внешний цикл do делает декомпрессию входных данных, пока inflate() не покажет, что она достигла конца сжатых данных, и что она вывела все выходные распакованные данные. В этом отличие от def(), которая обрабатывает весь входной файл. Если будет обнаружен конец файла (end-of-file, EOF) до окончания сжатых данных, то это означает, что поток сжатых данных неполон, и будет возвращена ошибка.

   /* Декомпрессия до окончания потока deflate или до завершения входного файла */
   do {

Мы читаем входные данные, и соответственно устанавливаем структуру strm. Если мы дошли до конца входного файла, то мы покидаем внешний цикл и сообщаем об ошибке, поскольку поток сжатых данных не завершен. Обратите внимание, что мы можем прочитать больше данных, чем потребила inflate(), если входной файл продолжается после окончания потока zlib. Для приложений, где потоки zlib встроены в другие данные, этот код должен быть изменен для возврата не используемых данных, или как минимум показать, сколько входных данных не было использовано, чтобы приложение знало, где брать данные после потока zlib.

      strm.avail_in = fread(in, 1, CHUNK, source);
      if (ferror(source)) {
         (void)inflateEnd(&strm);
         return Z_ERRNO;
      }
      if (strm.avail_in == 0)
         break;
      strm.next_in = in;

Внутренний цикл делает то же самое, что и внутренний цикл в def(), он вызывает inflate() до тех пор, пока генерирует выходные данные из предоставленных входных данных.

      /* Запуск inflate() на входных данных, пока не заполнится выходной буфер */
      do {

Также как и в def(), некое выходное пространство предоставляется для каждого вызова inflate().

         strm.avail_out = CHUNK;
         strm.next_out = out;

Теперь мы запускаем сам алгоритм декомпрессии. Здесь не нужно подстраивать параметр flush, поскольку формат zlib содержит в себе информацию о завершении сжатого потока. Главное отличие здесь в возвращаемых значениях, которым следует уделить внимание. Z_DATA_ERROR показывает, что inflate() обнаружила ошибку в сжатых данных формата zlib, что означает либо это данные не потока zlib (или их обработка началась с неправильной позиции), или данные были где-то повреждены на этапе сжатия или записи. Другая обрабатываемая ошибка Z_MEM_ERROR, которая может возникнуть из-за того, что реальное выделение памяти откладывается до тех пор, пока inflate() не нуждается в этом. Здесь также есть отличие от deflate(), где память выделяется при запуске deflateInit().

Продвинутые приложения могут использовать deflateSetDictionary() для первого deflate() с набором вероятных данных, чтобы улучшить сжатие первых 32K данных. Это упоминается в заголовке zlib, так что inflate() запрашивает предоставление такого словаря перед началом своей декомпрессии. Без этого словаря корректная декомпрессия невозможна. Для нашего примера приложения нет никаких идей, что это может быть за словарь, поэтому индикация Z_NEED_DICT преобразовыается в Z_DATA_ERROR.

Функция inflate() также может вернуть Z_STREAM_ERROR, что здесь невозможно, но в общем случае может быть проверено по тем же причинам, как было указано для def(). Z_STREAM_END будет проверяться позже.

         ret = inflate(&strm, Z_NO_FLUSH);
         assert(ret != Z_STREAM_ERROR);   /* состояние zlib не повреждено */
         switch (ret) {
         case Z_NEED_DICT:
            ret = Z_DATA_ERROR;
         case Z_DATA_ERROR:
         case Z_MEM_ERROR:
            (void)inflateEnd(&strm);
            return ret;
         }

Выходные данные inflate() обрабатываются идентично deflate().

         have = CHUNK - strm.avail_out;
         if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
            (void)inflateEnd(&strm);
            return Z_ERRNO;
         }

Внутренний цикл do-loop завершается, когда у inflate() больше нет выходных данных, что будет показано, что выходной буфер не заполняется, точно так же происходит и с deflate(). В этом случае мы не можем утверждать, strm.avail_in будет нулем, поскольку поток deflate может окончиться раньше, чем окончится файл.

      } while (strm.avail_out == 0);

Внешний цикл заканчивается, когда функция inflate() сообщит, что дошла до конца входного потока zlib, завершила декомпрессию и проверку целостности, и все данные предоставлены на выход. Это показывает inflate() возвращенным значением Z_STREAM_END. Внутренний цикл гарантированно вернет значение Z_STREAM_END, если последний прочитанный кусок файла содержит конец потока zlib. Так что если возвращенное значение не равно Z_STREAM_END, цикл продолжится для большего чтения входных данных.

      /* Завершение, когда inflate() сообщит от этом */
   } while (ret != Z_STREAM_END);

В этом месте декомпрессия полностью завершена, или мы вывалимся из цикла, если во входном файле больше нет данных. Если последний вызов inflate() вернул значение, не равное Z_STREAM_END, то поток zlib неполон, и будет возвращено сообщение об ошибке данных. Иначе произойдет успешный возврат из функции. Конечно, перед выходом будет вызвана функция inflateEnd(), чтобы не было утечки памяти.

   /* Очистка и возврат */
   (void)inflateEnd(&strm);
   return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}

Не этом заканчивается код, который напрямую использует zlib. Остальной код обслуживает командную строку программы, вызывает функции def() или inf(), сообщает об ошибках, которые происходят в этих функциях.

Функция zerr() используется для интерпретации кодов возможных ошибок, которые возвратят функции def() и inf(), и печати соответствующего сообщения об ошибке. Имейте в виду, что здесь декодируется только подмножество от возможных значений возврата из deflate() и inflate().

/* Сообщение об ошибке zlib или ошибке ввода/вывода */
void zerr(int ret)
{
   fputs("zpipe: ", stderr);
   switch (ret) {
   case Z_ERRNO:
      if (ferror(stdin))
         fputs("error reading stdin\n", stderr);
      if (ferror(stdout))
         fputs("error writing stdout\n", stderr);
      break;
   case Z_STREAM_ERROR:
      fputs("invalid compression level\n", stderr);
      break;
   case Z_DATA_ERROR:
      fputs("invalid or incomplete deflate data\n", stderr);
      break;
   case Z_MEM_ERROR:
      fputs("out of memory\n", stderr);
      break;
   case Z_VERSION_ERROR:
      fputs("zlib version mismatch!\n", stderr);
   }
}

Это функция main(), используемая для проверки def() и inf(). Команда zpipe просто дает канал между stdin и stdout, если не предоставлено аргументов, или дает канал декомпрессии, если используется zpipe -d. Если предоставлены любые другие аргументы, то не выполняется компрессия или декомпрессия, вместо этого выводится сообщение с подсказкой об использовании (usage). Например, zpipe < foo.txt > foo.txt.z запускается для сжатия, и zpipe -d < foo.txt.z > foo.txt для декомпрессии.

/* Компрессия или декомпрессия из stdin в stdout */
int main(int argc, char **argv)
{
   int ret;
   /* Вызов этих макросов позволяет избежать преобразований конца строк текста
      (файлы будут обрабатываться как двоичные) */
   SET_BINARY_MODE(stdin);
   SET_BINARY_MODE(stdout);
 
   /* Если нет аргументов, то делаем компрессию */
   if (argc == 1) {
      ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
      if (ret != Z_OK)
         zerr(ret);
      return ret;
   }
 
   /* Если указана опция -d, то делает декомпрессию */
   else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
      ret = inf(stdin, stdout);
      if (ret != Z_OK)
         zerr(ret);
      return ret;
   }
 
   /* Иначе выведется подсказка по командной строке */
   else {
      fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
      return 1;
   }
}

[Ссылки]

1. zlib 1.2.11 site:zlib.net.
2. zlib 1.2.11 Manual.

 

Добавить комментарий


Защитный код
Обновить

Top of Page