Сообщений 5    Оценка 330 [+1/-0]         Оценить  
Система Orphus

Apache Portable Runtime

Автор: Константин Топоров
Gehtsoft Group

Источник: RSDN Magazine #3-2009
Опубликовано: 07.03.2010
Исправлено: 10.12.2016
Версия текста: 1.0
Введение в APR
О чем эта статья
Что такое APR
Возможности APR
Кому может быть полезна APR
APR vs Boost
Функциональность APR
Управление памятью
Работа с файловой системой
Работа с сетью
Потоки, процессы, синхронизация
Коллекции
Работа со строками
Дата и время
Разделяемая память
Динамическая загрузка
Остальное
Расширения APR
APR-Iconv
APR-Util
Заключение

Домашняя страница проекта: http://apr.apache.org

Введение в APR

О чем эта статья

Данная статья предлагает первоначальное знакомство с кроссплатформенной библиотекой Apache Portable Runtime (APR). Этот продукт известен прежде всего тем, что используется в составе популярного Web-сервера Apache.

В статье не содержится примеров кода или детального обсуждения проблем. Ее цель – рассказать о самом существовании этой библиотеки и бегло описать ее возможности. К сожалению, библиотека APR не столь широко известна, как некоторые другие подобные продукты, а разработчики не снабдили ее обширной документацией. Это можно объяснить тем, что основное предназначение APR – оптимизировать работу популярного Web-сервера, а не быть самостоятельным продуктом. В данной статье будут выделены функциональные слои, из которых сложена библиотека, и рассмотрены возможности самой библиотеки в различных аспектах.

Статья носит обзорный характер, поэтому углубления в детали не будет.

Что такое APR

APR – кроссплатформенная библиотека, написанная на чистом C. Изначально она представляла собой расширение стандартной библиотеки, которое использовалось в Web-сервере Apache. Позже APR выделяется в самостоятельный продукт и значительно расширяется. APR распространяется под лицензией Apache 2.0, что не создает никаких проблем для использования библиотеки в коммерческих или некоммерческих продуктах.

Как видно из названия, разработкой APR занимается фонд Apache.

Apache распространяет сборки для: UNIX, Windows, MacOS, OS2, Netware.

Использовать APR лучше всего именно вместо стандартной библиотеки языка C. Функционально APR частично перекрывает стандартную библиотеку и во многом ее расширяет и дополняет. Также предлагается много функциональности, которая никак не отражена в стандартной библиотеке, в том числе кроссплатформенную реализацию различных системно-зависимых операций.

Поскольку APR написана на C, она имеет соответствующий интерфейс и лишена сложных конструкций, присущих подобных библиотекам, написанным на C++.

В настоящее время APR имеет две дополнительные библиотеки – APR-Utul и APR-Iconv. Первая содержит разнообразную функциональность, которая не вошла в основной модуль, а вторая предназначена для преобразования строк в различные кодировки.

Сам термин APR может иметь два значения в зависимости от контекста. В узком смысле это только базовый модуль, в широком – набор из всех трех модулей.

Возможности APR

Итак, APR предоставляет все возможности стандартной библиотеки:

Кроме того, APR предлагает:

Расширения APR дополняют этот список следующими пунктами:

И все это работает на Windows- и UNIX-системах (и не только) и абсолютно бесплатно!

Кому может быть полезна APR

Вам следует обратить внимание на APR, если:

Наличие таких требований означает, что APR определенно принесет вам немалую пользу и сэкономит массу времени.

Вместе с тем, потребность в таком базовом модуле не означает выбора именно APR. В мире существует по крайней мере еще несколько подобных продуктов. Но не все из них удовлетворяют вышеизложенным условиям, и порой APR оказывается единственным решением.

Ниже приведено краткое сравнение APR с самым популярным аналогом.

APR vs Boost

Безусловно, самым популярным кроссплатформенным сборником утилит является boost. Насколько APR сможет с ним тягаться? Если сравнивать их напрямую, то шансов у APR не видно.

Потому что:

Однако у APR есть свои плюсы:

Функциональность APR

Управление памятью

Управление памятью в APR построено на концепции пулов памяти. Пул памяти – иерархическая структура, которая содержит в себе как дочерние пулы, так и собственно память, выделенную из этого пула. Пользователь может создавать и удалять дочерние пулы, а также выделять куски памяти из пула с помощью функции apr_palloc (apr_pcalloc). Основное отличие памяти из пула от памяти из стандартной кучи в том, что выделенные из пула фрагменты памяти не могут быть освобождены индивидуально. Выделенная в пуле память освобождается только при удалении пула. Это накладывает заметный отпечаток на стиль программирования. Здесь не нужно четко сопоставлять вызов free на каждый вызов malloc. Надо уяснить себе время жизни фрагмента памяти и на основе этого планировать жизненный цикл пула, на котором эта память будет выделяться.

Пул памяти – это первая концепция, с которой придется столкнуться при работе с APR. И с пулами придется работать всегда, поскольку в APR нет иного средства выделения памяти и любая, абсолютно любая операция с выделением памяти требует пула. Соответственно, многие объекты, такие как потоки, мьютексы ассоциированы с заданным пулом на протяжении всего своего жизненного цикла. Поэтому проблема проектирования иерархии пулов с разным временем жизни и разным предназначением является важнейшей архитектурной задачей при проектировании приложения.

APR предлагает достаточно развитые средства для управления системой пулов памяти. С пулом может быть ассоциирована функция обратного вызова, которая используется при освобождении пула. С пулом могут быть ассоциированы произвольные пользовательские данные. Пулу может быть присвоено имя. В отладочной версии APR существуют функции, которые позволяют выяснить размер выделенной памяти в пуле, является ли один пул предком другого, найти пул по фрагменту памяти, гарантировать дочернему пулу время жизни, совпадающее с родительским.

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

Работа с файловой системой

APR предоставляет практически полный набор возможностей взаимодействия с файловой системой. Кроме того, большое внимание уделено удобству использования.

Если операции открытия, создания, чтения, записи, позиционирования подобны своим аналогам из стандартной библиотеки по интерфейсу и возможностям, то многие операции существенно упрощены.

Например, чтобы дописать текст в файл, есть функция

apr_file_append(
  constchar *from_path,
  constchar *to_path,
  apr_fileperms_t perms,
  apr_pool_t *pool);

Для такой часто нужной операции как создание дерева каталогов:

apr_dir_make_recursive(
  constchar *path,
  apr_fileperms_t perm,
  apr_pool_t *pool);

И, кроме того, есть ряд полезных функций поиска файлов по шаблону, склеивание/разбиение списка файлов, ну и конечно полный набор стандартной функциональности для работы с файлами и каталогами.

Работа с сетью

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

Использование сетевого API APR – замечательная возможность избавиться от этой головной боли, взяв готовое решение, проверенное временем и работающее в составе одного из самых популярных Web-серверов.

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

Опрос множества сокетов поддерживается функцией apr_poll, которая по своему интерфейсу близка одноименной Linux-команде. Для операционных систем, которые не поддерживают команду poll, реализация строится через другие системные механизмы: select, kqueue или port. Какую реализацию использовать, определяется на этапе компиляции APR через команды препроцессора.

Также в полной мере поддерживается переключение сокетов в неблокирущий/блокирующий режимы.

Потоки, процессы, синхронизация

Все что связано с многопоточностью, дочерними процессами, синхронизацией представлено в APR во всей полноте.

Основным достоинством является опять же абсолютная кроссплатформенность.

Поток создается с помощью функции:

apr_thread_create(
  apr_thread_t **new_thread,
  apr_threadattr_t *attr,
  apr_thread_start_t func,
  void *data,
  apr_pool_t *cont)

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

И в остальном работа с потоками проста и интуитивно понятна.

Основным объектом синхронизации между потоками выступают мьютексы. Мьютексы делятся на три класса: мьютексы потоков в одном процессе (apr_thread_mutex), мьютексы процессов (apr_proc_mutex) и мьютексы как для потоков, так и для процессов (apr_global_mutex). Универсальное использование глобальных мьютексов не рекомендуется из соображений производительности. Если посмотреть реализацию, то можно увидеть, что глобальный мьютекс содержит в себе мьютекс потока и мьютекс процесса, и работает с ними одновременно. В случае, если APR собирается без поддержки потоков, то глобальный мьютекс сводится к процессному.

Еще один широко распространенный объект – событие. В APR оно называется condition (apr_thread_cond). Как видно из его названия, событие может работать только в рамках одного процесса.

Особенностью объектов синхронизации в APR является то, что их реализация под unix основана на библиотеке pthread. Оттуда наследуется одно очень неприятное ограничение – невозможность ожидания нескольких объектов одновременно. К сожалению, в общем случае через APR эта задача не решается.

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

Что касается управления процессами, то и здесь возможность APR весьма велики. Можно создавать процессы, перенаправлять ввод/вывод в файлы, устанавливать рабочий каталог, ожидать завершения процесса, завершать процесс принудительно. Для операционных систем, поддерживающих fork, предусмотрена аналогичная функция. Кроме того, в наличии некоторая функциональность контроля времени жизни наблюдаемых процессов (посредством функций apr_proc_other_child_*).

Для unix систем предусмотрены функции обработки сигналов. Можно выбрать поток, в котором обрабатываются все сигналы. Кроме того, APR позволяет перехватить любой сигнал, поддерживаемый операционной системой. Иными словами, разработчики на unix не будут чувствовать себя ограниченными.

Коллекции

Огромной проблемой стандартной библиотеки C является отсутствие коллекций. В C++ есть stl, который покрывает большинство потребностей, но если вы пишете на чистом C… APR решает эту проблему, предлагая следующие виды коллекций:

Массив (apr_header_array). Представляет собой динамический массив для хранения указателей.

Хэш-таблица (apr_hash). Может хранить бинарные ключи и бинарные данные. Возможно задание произвольной хэш-функции.

Таблица строк (apr_table). Хранит строковые ключи и значения. Содержит богатые функции управления дублирующимися записями, объединения таблиц.

Закольцованный список (apr_ring). Кольцо указателей на основе двунаправленного списка.

Как видно, предоставляемых возможностей более чем достаточно, чтобы отказаться от создания собственных велосипедов. Эти контейнеры достаточно просты и не потокобезопасны. В библиотеке apr-util реализована блокирующая очередь, предназначенная как раз для многопоточного использования.

Работа со строками

В аспекте работы со строками преимущество APR в функциональности над стандартной библиотекой не столь велико. Разница лишь в том, что операции клонирования (строк или памяти) в APR делаются на заданном пуле. Но есть несколько интересных функций, например:

apr_collapse_spaces(char *dest, constchar *src)

Убирает пробелы из заданной строки.

apr_tokenize_to_argv(
  constchar *arg_str, 
  char ***argv_out,
  apr_pool_t *token_context)

Как видно из названия, преобразует строку с аргументами в массив строк.

Кроме того, здесь же есть функции преобразования чисел в строки (и обратно), а также функции форматированного вывода в строку.

Дата и время

Кроме всего уже упомянутого, APR предлагает исчерпывающую систему управления датой и временем. И это очень здорово, поскольку возможности стандартной библиотеки в этом аспекте очень скромны. В довершение всего, интерфейс этих функций продуман очень хорошо и сочетает в себе наилучшие подходы к работе с датами, позаимствованные из иных реализаций. А заодно устраняет многие неудобства, с которыми приходится сталкиваться в этих самых альтернативах.

Раскроем тему немного шире:

APR содержит сериализованный тип даты apr_time_t. Это тип представляет собой длинное целое, а его физический смысл – количество микросекунд с полночи 01.01.1970. Уже сам выбор такой единицы измерения несет в себе немалые преимущества. Во-первых, это высокая точность, достаточная на все случаи жизни в абсолютном большинстве приложений. Во-вторых, это стопроцентная совместимость с java. Теперь не надо писать конвертеры из java-формата. Конвертация тривиальна.

Имеется в наличии структурный тип даты apr_time_exp_t. Достоинства этого типа в его подробности: он содержит микросекунды и смещение относительно GMT плюс стандартные поля. Нет нужды говорить, что эти типы даты в APR легко конвертируются один в другой.

Также в данной библиотеке есть функции изменения даты/времени в зависимости от часовой зоны.

А вот функция для совместимости со стандартной библиотекой:

apr_time_ansi_put(apr_time_t *result, time_t input);

Конвертация односторонняя, APR не приветствует использование внешних типов.

А еще есть операции форматирования даты в строку, выполненные в традиционном стиле:

apr_rfc822_date(char *date_str, apr_time_t t)
apr_ctime(char *date_str, apr_time_t t)
apr_strftime(
  char *s, 
  apr_size_t *retsize,
  apr_size_t max,
  constchar *format,
  apr_time_exp_t *tm) 

Можно еще сказать несколько слов о том, чего в APR нет. Нет разбора строки с датой - ни в каком виде. Арифметические операции требуют приведения в сериализованный тип. Нет управления временными зонами. Но с другой стороны, все это уже выходит за рамки базовых потребностей, которые в данном аспекте удовлетворены наилучшим образом.

Кроме того, что-то на эту тему есть в расширениях. Читаем ниже.

Разделяемая память

APR поддерживает такие важные средства межпроцессной коммуникации как разделяемая память и файлы, отображаемые в память.

Поддерживаемая функциональность стандартна: создать сегмент памяти (apr_shm_create), удалить сегмент (apr_shm_destroy), присоединиться к сегменту (отсоединиться), получить базовый адрес и размер.

Интерфейс для файлов, отображаемых в память (memory mapped files) очень лаконичен и уступает по возможностям своим аналогам из Unix и Windows. Почему так случилось, точно не известно. Но можно предположить, что эта функциональность не была широко востребована, и таким образом простейшая реализация осталась в текущей версии APR. Однако даже то что есть, позволяет без проблем отображать файл и работать с его данными. А вот отсутствие функции для синхронизации памяти с файлом без закрытия маппинга не очень удобно.

Динамическая загрузка

Проблемы динамических модулей и поиска функций в них не остались незамеченными разработчиками APR. Функциональность очень проста и понятна: загрузить модуль (apr_dso_load), выгрузить модуль (apr_dso_unload), найти функцию в модуле (apr_dso_sym), получить ошибку (apr_dso_error). Этого достаточно, чтобы писать исключительно гибкие приложения с низкой связностью между компонентами. В общем, если вы привыкли использовать reflection в языках более высокого уровня или хотите развязать модули приложения на уровне компиляции – то эти четыре функции сэкономят вам время на написание нового кроссплатформенного решения, которое благодарные исследователи в будущем классифицируют как “велосипед”.

Остальное

Незатронутыми остались:

Ничего сложного в этих темах не кроется, но кому-то эта функциональность может оказаться полезной. Поэтому неплохо бы о ней знать.

Расширения APR

APR-Iconv

Это узкоспециализированный модуль для конвертации строк из одной кодировки в другую. Поддерживает множество кодировок. Точный их список можно посмотреть в заголовках библиотеки. Версия apr-iconv 1.2.1 поддерживает порядка 400 различных кодировок.

Такое богатство возможностей спрятано за весьма простым и кратким интерфейсом:

apr_iconv_open(
  constchar *to,
  constchar *from,
  apr_pool_t *pool,
  apr_iconv_t *cd);

Создать дескриптор преобразования. Параметры говорят сами за себя: исходная кодировка, целевая кодировка, пул памяти.

apr_iconv(
  apr_iconv_t cd,
  constchar **inbuf,
  apr_size_t *inbytesleft,
  char **outbuf,
  apr_size_t *outbytesleft,
  apr_size_t *translated);

Собственно преобразовать строку. Параметры тоже очевидны: дескриптор, созданный на предыдущем шаге, входные параметры, результаты.

apr_iconv_close(apr_iconv_t cd, apr_pool_t *pool);

Завершить работы с дескриптором.

Очевидно, что данный модуль невероятно полезен, если есть потребность перекодирования строк и необходимо не быть привязанным к операционной системе.

APR-Util

В этом модуле собраны всевозможные функции, не вошедшие в основную библиотеку. В отличие от APR, претендующего на роль фундаментального рантайма для приложения, APR-Util содержит совершенно разнообразную функциональность, которую нельзя классифицировать каким-либо одним словом. И если базовая APR такова, что достаточно развитое приложение может использовать все ее возможности, то из APR-Util может быть востребована лишь небольшая часть функций, в зависимости от потребностей конкретного приложения.

Пулы потоков

Совершенно необходимая для многопоточного приложения функциональность. Она тем более ценна, что в настоящее время не очень легко найти отлаженную реализацию на языках C/C++.

Пул потоков создается функцией

apr_thread_pool_create(
  apr_thread_pool_t **me, apr_size_t init_threads, 
  apr_size_t max_threads,  apr_pool_t *pool);

Как видно, и здесь не обходится без пула памяти.

Добавить задачу:

apr_thread_pool_push(
  apr_thread_pool_t *me, 
  apr_thread_start_t func,
  void *param, 
  apr_byte_t priority, 
  void *owner);

Основной аргумент здесь – указатель на функцию задачи func, прототип которой совпадает с функцией потока из базового модуля APR (чего, в общем-то, и следовало ожидать).

Кроме того, стоит обратить внимание на то, что задачи разделяются по приоритетам.

Выполнить задачу с задержкой:

apr_thread_pool_schedule(
  apr_thread_pool_t *me,
  apr_thread_start_t func,
  void *param,
  apr_interval_time_t time,
  void *owner);

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

Кроме этих ключевых функций, пул потоков позволяет:

Очередь

Еще одним элементом, необходимым для многопоточного приложения, является потокобезопасная очередь. APR-Util предоставляет такой объект в форме блокирующей FIFO-очереди с ограничением на количество элементов.

Очередь блокирует добавление элемента, если она уже полна. Также блокируется снятие элемента, если очередь пуста.

Такое жесткое поведение может создавать некоторые неудобства, вследствие чего предусмотрены функции apr_queue_trypush и apr_queue_trypop для немедленного возврата управления очередью.

Также имеется возможность прервать все операции очереди командой apr_queue_interrupt_all.

XML

APR-Util содержит DOM-парсер. Он может строить DOM-модель из файла целиком (apr_xml_parse_file) или из набора строк (apr_xml_parser_feed, apr_xml_parser_done). Строки можно добавлять постепенно, что позволяет обрабатывать XML-данные больших размеров.

Существует и обратная операция: преобразование DOM-элемента в текст.

Реализация разбора построена на различных SAX-парсерах. Выбор парсера происходит на этапе компиляции APR-Util. По умолчанию используется широко распространенный Expat.

Кроме собственно DOM-модели и парсера, есть еще полезные функции, имеющие отношение к XML:

apr_xml_quote_string(apr_pool_t *p, constchar *s, int quotes); 

Делает строку пригодной для помещения внутрь XML, т. е заменяет все символы (“, <, > ) на их разрешенные аналоги.

Элементы криптографии

APR-Util содержит несколько широко используемых алгоритмов кодирования и хэширования данных. Однако их набор крайне узок, что объясняется видимо тем, что были добавлены только те алгоритмы, без которых уж совсем не обойтись:

Тем не менее, этого достаточно для многих прикладных задач.

LDAP

LDAP представлен в APR-Util как универсальный фасад для ряда широко распространенных LDAP-клиентов, сглаживая тем самым мелкие различия, присутствующие в самих этих клиентах.

Однако сам APR-Util встроенного клиента не содержит. APR-Util умеет работать с LDAP как по открытому протоколу, так и по защищенному.

Выбор базового LDAP-клиента осуществляется на этапе сборке проекта. Поддерживаются все основные клиенты LDAP, в том числе OpenLDAP, Microsoft, Novell, Solaris, Tivoli.

Возможно собрать APR-Util вообще без поддержки LDAP.

Memcache

Если рассмотренная выше LDAP-функциональность является лишь фасадом к реальным клиентам, то для memcache в APR-Util имеется полноценный клиент. Почему это так, достаточно понятно – протокол memcache несравнимо проще LDAP.

Предоставляемые функции практически исчерпывающи. Сюда входит:

SQL

Еще один полезный фасад в APR-Util закрывает пользователя от деталей взаимодействия с базой данных через SQL.

Называется этот интерфейс APR-DBD (DataBase Driver), и ключевым компонентом в нем является драйвер базы данных. APR-Util имеет свой интерфейс драйвера, в котором содержатся вся основная функциональность, связанная с операциями над базой данных:

Интерфейс APR-DBD в общем является лишь фасадом к драйверу, и все реальные операции выполняет именно драйвер.

Разработчики APR позаботились о реализациях драйвера для наиболее популярных СУБД. В составе APR-Util уже имеются драйверы для:

Драйверы для остальных СУБД придется разрабатывать самостоятельно. Технически это не очень сложная задача для любой СУБД, предоставляющей C/C++-интерфейс. Самодельный драйвер должен содержаться в отдельном модуле и подгружаться в систему динамически средствами самого APR-Util.

Кроме того, при сборке APR-Util можно отключить все ненужные драйверы.

Таким образом, APR предоставляет удобный интерфейс абстрагирования от конкретной СУБД. Конечно, этот интерфейс является обобщенным, и особенности различных реализаций здесь стираются. Но если нужен стандартный интерфейс и есть риск миграции базы данных – тогда APR-DBD очень к месту.

DBM интерфейс

Наряду с SQL-интерфейсом присутствует еще и интерфейс хэш-таблиц доступа к данным, известный как DBM. Архитектура в общем одинакова с DBD. Так же за внешним интерфейсом находится драйвер, есть уже готовые драйверы, возможны драйверы самодельные, можно собирать APR без лишних драйверов.

Поддерживаются следующие реализации DBM:

Преимущества и недостатки те же, что и у DBD. За универсальный интерфейс приходится платить преимуществами конкретной реализации. Особенно страдает от этого Berkeley DB, многие очень важные возможности которой остаются за фасадом.

Остальное

Не рассмотренными выше остались:

Заключение

На этом краткий перечень возможностей APR можно завершить. Стоит однако отметить, что проект продолжает развиваться, и в открытом доступе можно увидеть запросы на расширение существующей функциональности, обсуждение особенностей переноса на другие системы, деталей реализации в различных аспектах и известных дефектов. Таким образом, можно ожидать, что в будущем возможности APR станет еще более обширными.

Ознакомиться со всем этим можно по адресу: http://mail-archives.apache.org/mod_mbox/apr-dev.


Эта статья опубликована в журнале RSDN Magazine #3-2009. Информацию о журнале можно найти здесь
    Сообщений 5    Оценка 330 [+1/-0]         Оценить