Диссертация (1090660), страница 23
Текст из файла (страница 23)
Для передачи данных в шаблонизатор используется универсальный тип, называемый CDT (Common Data Type),к которому, теоретически, возможно привести данные любого типа. В PHP и PERL,благодаря отсутствию жесткой типизации данных, использование CDT не вызываетникаких проблем, но не в C++. Из-за строгого контроля типов данных в C++, в реализации CTPP2 данные могут быть представлены как значение без имени (string),переменная (HASH_VAL) и массив (ARRAY_VAL). В результате перегрузка оператора присваивания была реализована только для встроенных типов и элементов типаCDT.Листинг 4.4.
Сущность Common Data TypeCTPP::CDT data = "Данные";CTPP::CDT value = data;В другом примере, класс CDT хранит переменную “name” в виде HASH_VAL созначением строкового типа (здесь: строка «Шаблонизатор CTPP2»). Кроме того, значение может быть и любого другого встроенного в язык C++ типа, а также экземпляром класса CDT. Так, во второй строке происходит переопределение новым значениемиз переменной “data” неопределённого типа, что является вполне допустимым, если“data” является экземпляром класса CDT.Листинг 4.5.
Пример CDT с хэшемCTPP::CDT var["name"] = "Шаблонизатор CTPP2";var["name"] = data;Так же есть реализация функции Push_Back() для заполнения массивов, причемэлементом массива может быть переменная типа CDT:Листинг 4.6. Пример заполнения массиваCTPP::CDT mas;136for(int i = 0; i<10; i++){mas.Push_Back(i);}mas.Push_Back(data);Переменная mas заполняется элементами массива, но в представлении шаблонизатора не имеет имени, по которому к ней можно обратиться. Для того чтобы иметьвозможность обратиться к массиву по имени необходимо присвоить его переменнойтипа CDT:Листинг 4.7.
Присвоение переменной типа CDTCTPP::CDT name_mas;name_mas["array"] = mas;В результате передача сложных структур данных в шаблонизатор становитсянетривиальной задачей. Необходимо определять является ли данная переменная массивом, и, если да, то возникает необходимость создавать дополнительный элементтипа CDT с последующим его заполнением, а если те, в свою очередь, являются элементами массива, то возникает необходимость в еще одной переменной типа CDT ит.д.Простым решением данной проблемы является генерация JSON строки или файлав этом формате с необходимыми данными, а затем передача его в функцию шаблонизатора, которая, в свою очередь, произведет синтаксический анализ JSON и, приего корректности, CDT будет заполнен.
Данный вариант достаточно прост, но несетв себе дополнительные накладные расходы, ведь возникает необходимость в лишнемшаге на составление JSON’а, да и анализ его достаточно дорогостоящая операция почасти ресурсов.Другой вариант заключается в построении структуры данных, позволяющей легкоопределить глубину вложенности данных, а затем, используя рекурсивно вызываемуюфункцию, заполнить CDT путём применения стандартных функций шаблонизатора.При условии выбора оптимальной структуры данных второй способ заметно эффективней и по памяти, и по использованию процессорного времени во время работы,и при заполнении структуры данных нет возможности допустить синтаксическуюошибку, в отличие от возможных проблем при составлении JSON строки.В ходе работы с шаблонизатором CTPP2 в реализации на C++ для решения даннойпроблемы с передачей данных было принято решение использовать второй метод,а именно согласно поступившим данным составлять вектор структуры, в которойхранится глубина текущего элемента, имя элемента и его значение.137Листинг 4.8.
Структура с именем элемента и его глубинойstruct data {int depth;std::string name;std::string value;}Затем была написана рекурсивно вызываемая функция, в которую передается вектор структуры, представленной выше. Данная функция поэлементно просматриваетвектор и заполняет CDT. Если значение (value) текущего элемента вектора являетсяпустой строкой, а значение глубины (depth) текущего элемента вектора меньше значения глубины следующего элемента вектора, то данный элемент является массивом.В этом случае функция рекурсивно вызывает себя для его заполнения, иначе элемент является простой переменной и сразу добавляется в CDT. После добавления вCDT элемент вектора удаляется, освобождая память, чего не происходит при работес JSON’ом.4.4Разработка архитектуры программного комплексасинхронизатора4.4.1Обоснование целесообразности разработкиЛогика работы языка BML хранится в файлах в специализированной директориии исполняется интерпретатором языка.
Со структурами данных BML могут быть связаны многочисленные таблицы с достаточно сложными связями. Если разработчикпожелает поменять структуру BML, это может повлечь значительные изменения вовнутренних архитектурах, в том числе и в структуре базы данных. Их перестроение может занимать продолжительное время.
Таким образом, если интерпретируемаялогика и процесс перестроения будут выполняться линейно, это повлечет за собойсерьезные задержки в выполнении пользовательского запроса [89].Для решения поставленной задачи были разделены механизмы перестроения внутренних структур и непосредственной обработки пользовательских запросов. Перестроение асинхронно во времени выполняет синхронизатор. Задача синхронизатора –обеспечить безболезненный переход от одной логики к другой, в то время, как интерпретатор хранит в оперативной памяти старые данные, загруженные до обновления, иисполняет логику на их основе до полного перестроения.
После прохождения обнов-138ления интерпретатор может начать исполнять логику немедленно, согласно изменениям разработчика, не беспокоясь при этом, что возникнут ошибки при использованииобновленных структур.4.4.2Разработка общего алгоритма работыСинхронизатор работает в режиме UNIX-демона. При запуске программного модуля создается дополнительный экземпляр процесса, ожидающего входящие соединения от клиента. В качестве клиента выступает интерпретатор, исполняющийся всреде FastCGI или CGI.Интерпретатор не работает напрямую с файлами BML. Данную функцию выполняет синхронизатор, загружая их из рабочей директории проекта.
Затем происходитсинтаксический анализ и последующее сохранение полученных структур в оперативной памяти. Порции этих данных передаются по запросу при подключении клиентачерез сокет согласно запрошенному пользователем адресу URL.Синхронизатор периодически просматривает рабочую директорию на предмет изменения файлов. Для этого в нем создан специальный массив, элементами которого являются структуры, каждая из которых хранит имя файла, его дату измененияи контрольную сумму.
Сначала происходит сверка количества файлов в директориис количеством элементов массива, затем осуществляется просмотр директории, гдепроверяется факт изменения даты и времени у файлов. Если изменение произошло,осуществляется подсчет контрольной суммы файла по алгоритму CRC32 [108]. Принесовпадении полученного значения и ранее сохраненного в оперативной памяти, либо если количество элементов массива не совпадает с количеством файлов, считается,что структура проекта была изменена.
В таком случае происходит полное перестроение логической структуры кода BML в памяти путем последовательного считываниякаждого файла и синтаксического анализа их содержимого. На рис. 4.8 показан общийалгоритм работы синхронизатора.4.4.3Анализ технологий межпроцессного взаимодействияВ основу разработки эффективного программного комплекса, обеспечивающегосинхронизацию данных, входит грамотно решенная задача распределения выполняемых функций во времени. Так, функционал перестроения базы данных не можетвыполняться вместе с основным алгоритмом, ведь само перестроение может зани-139мать продолжительное время, что приведет к блокировке работы программы при еепоследовательном исполнении.Алгоритм перестроения базы данных в проекте выделен в дочерний процесс, который создается системным вызовом fork() (см.
рис. 4.8) и отделен от основной логикисинхронизации. В родительском процессе сохраняется PID дочернего. Идентификаторсбрасывается, как только дочерний процесс завершается.Для проверки факта завершения вызывается системный вызов waitpid() с опцией“WHOHANG”, позволяющей не дожидаться окончания процесса, а только получитьего состояние [62]. Признак завершения процесса – нулевой код возврата системного вызова. Также данный системный вызов возвращает код статуса завершения дочернего процесса. Сообществом разработчиков принято правило, согласно которомунулевой код возврата обозначает успешное завершение процесса. Подпроцесс перестроения БД выполняется по такому же принципу. В случае возникновения ошибоквозвращается число, отличное от нуля.
К ошибкам подобного рода, к примеру, могутотноситься неполадки работы СУБД или неудачная авторизация.Важной особенностью также является то, что синхронизатор сохраняет логикуBML в двух структурах: «A» и «B». Из структуры «A» считываются данные, передающиеся впоследствии клиентскому приложению (интерпретатору), а в структуру«B» – новые данные при обнаружении изменений в исходных файлах. После записи происходит проверка полученной структуры и сопоставление ее с базой данных,которая перестраивается согласно полученным изменениям (рис. 4.9). В процессе перестроения всем новым объектам присваивается постфикс “$modified”. После получения системным вызовом waitpid() информации о завершении дочернего процессаструктуры «A» и «B» меняются местами.
Стоит обратить внимание, что присвоениепостфикса “$deleted” устаревшим объектам и удаление постфикса “$modified” у новых происходит уже в родительском процессе, также после получения информации озавершенном дочернем. В отличие от громоздкого алгоритма копирования и перестроения данных, который был вынесен в отдельный процесс, процедура переименованиясущностей имеет гораздо меньшие накладные расходы и может работать синхронно сосновной логикой. Это единственная часть алгоритма взаимодействия с базой данныхв родительском процессе. Такая архитектура обусловлена «мягким» переходом междуконфигурациями, что гарантирует доступность данных на основе старой логики, покавыстраивается обновленная структура.1404.4.4Проведение нагрузочного тестированияКак уже было сказано, для межпроцессного взаимодействия в проекте используются сетевые сокеты.