Сервер приложений -- это программа, предоставляющая другим программам некоторый перечень "услуг", как правило через сетевые соединения.
В качестве примера можно привести сервер биржевых котировок. В бытность моей работы на брокерскую фирму у нас был сервер котировок, который поставлял информацию 11-ти маклерам в нашем отделе. На компьютере у каждого маклера работала клиентская программа. По мере необходимости, она обращалась к серверу и получала от него необходимые сведения.
В данной статье я хочу представить код, который может служить каркасом для создания серверов приложений, написанных на C++. Сначала я расскажу о том, как работать с таким "каркасом", а затем, для любознательных, постараюсь объяснить внутреннее устройство и принципы работы. Надеюсь, что по прочтении данной статьи вы будете достаточно ясно понимать принципы работы этого каркаса, служащего платформой для создания клиент-серверных приложений на C++, и сможете использовать ее код в своих разработках.
Прежде всего, для того, чтобы попробовать поработать с кодом, вам потребуется установить Expat XML Parser (Библиотека грамматического разбора предложений языка разметки XML). Код предлагаемой вашему вниманию платформы "заворачивает" в XML данные, циркулирующие между серверами и клиентами, а библиотека Expat используется для грамматического разбора XML. Сразу замечу, что примеры, приводимые в данной статье, тестировались с Expat версии 1.95.2.
Платформа предоставляет в распоряжение программиста четыре основных класса:
Класс request -- это запрос, передающийся от клиента к серверу. Класс имеет свойство "name" и набор параметров запроса -- "params". Для большей простоты можно представить себе передачу запроса как вызов метода, где "name" -- имя метода, а "params" -- набор передаваемых ему аргументов.
Класс reply представляет собой ответ сервера на полученный запрос. Он имеет свойство "value" и список ошибок "errors". Для большей простоты ответ можно представить себе как значение, возвращаемое при вызове метода-запроса, где "value" -- фактическое возвращаемое значение, а "errors" -- список ошибок, возникших в процессе выполнения тела метода.
Класс server -- это "рабочая лошадка" платформы. Вы можете использовать его в качестве "родительского" при создании своих собственных серверов. Основной интерес для вас будут представлять два метода, реализуемые данным классом -- "handle_request" и "run". Метод "run" запускает сервер и начинает принимать соединения с клиентами. Метод "handle_request" отвечает за обработку всех поступивших от клиентов запросов. При создании своего сервера метод "handle_request" необходимо "перекрыть" в "дочернем" классе.
И наконец -- класс client, который отвечает за передачу запроса на сервер. При создании экземпляра класса, конструктору передаются сетевое имя сервера и номер порта, а затем, с помощью метода "send_request" можно отправлять на сервер свои запросы.
После того, как мы кратенько прошлись по основным классам, можно взглянуть на работающий пример. Следующие два файла содержат реализацию простейших сервера и клиента:
В программе simple_quoter_server.cpp определяется класс "simple_quoter_server" -- наследник от xmlrpc::server. В нем перекрывается метод "handle_request", который вызывается всякий раз, когда от клиента поступает запрос. Если запрос содержит параметр "ticker" со значением "RHAT", то возвращается ответ со свойством "value", содержащим текущую цену этого продукта. Если возникают какие либо ошибки, то они заносятся в список "errors".
В программе simple_quoter_client.cpp просто создается экземпляр класса xmlrpc::client, создается и заполняется запрос и затем, вызовом метода "send_request", запрос передается на сервер.
В предлагаемом вам примере, для оформления сообщений, которыми обмениваются клиенты и серверы, используется язык разметки XML. Прежде чем написать хотя бы строчку кода, я задумался над тем, как должны выглядеть запрос и ответ на языке XML. И вот к чему я пришел:
// // <request> // <name></name> // <params> // <param> // <name></name><value></value> // </param> // </params> // </request> // // // <reply> // <return_value></return_value> // <errors> // <error></error> // </errors> // </reply> //
Затем я приступил к изучению принципов работы с Expat XML Parser. Я нашел замечательную статью Using Expat на www.xml.com, которая описывает основные приемы работы с этой библиотекой. Прочитав ее, я написал следующий класс, инкапсулирующий необходимую мне функциональность:
Класс "node" представляет собой "узел" XML-документа. Ему передается XML-строка посредством вызова метода "load_xml", в котором производится разбор строки и создается образ XML-документа в памяти. Узел может иметь набор дочерних узлов, таким образом у вас имеется возможность "прикручивать" к документу дочерние узлы. Если возникает необходимость, то XML-строку можно запросить методом "get_xml".
Далее были написаны два класса для представления XML-определений, о которых я упоминал ранее:
Обратите внимание -- оба класса имеют методы "get_xml" и "load_xml". Метод "get_xml" возвращает строку, содержащую внутреннее представление класса, а "load_xml" записывает ту же самую строку в класс. Таким образом, оба этих класса могут быть переведены в XML-представление и обратно.
Сделано это для того, чтобы локализовать работу с XML в одном месте. И класс "client" и класс "server" вызывают методы "load_xml" и "get_xml" для генерации сообщений, которыми они обмениваются между собой. Если вы решите отказаться от использования XML в своих программах -- вам достаточно будет переписать эти два метода в классах "server" и "client".
Обсуждаемый пример платформы использует для передачи данных по сети три класса:
Описание этих классов вы найдете в январском выпуске "Linux Gazette". Названия классов я изменил, но реализация осталась прежней.
Класс "server", рассказ о котором пойдет ниже, обслуживает запросы от клиентов в отдельных потоках выполнения [Их часто называют "в лоб": "треды". -- Прим. ред.]. Для работы с потоками платформа предоставляет следующие классы:
Класс "thread" представляет собой отдельный поток. Концепцию класса "thread_group" я заимствовал из Boost.Threads Library. По сути: если вам требуется создать отдельный поток -- используйте класс "thread", группу потоков -- класс "thread_group".
Рекомендую при первом удобном случае посетить сайт Boost. Boost -- это группа энтузиастов, которая занимается разработкой кросс-платформенных библиотек на языке C++. Особо подчеркну, что некоторые члены этой группы входят в состав Комитета по Стандартизации Языка Программирования C++ (C++ Standards Committee), а это означает, что библиотеки, которые вы найдете на этом сайте, будут отличаться высоким качеством.
Определение и реализация класса "server" находится в следующих файлах:
Обратите внимание на определение класса "accept_thread" в начале *.cpp файла. Вы еще не забыли, что я назвал класс "server" "рабочей лошадкой"? Хочу признаться в том, что несколько погрешил против истины. Вся работа класса "server" заключается в создании массива объектов класса "accept_thread" и ожидании в бездействии пока они выполнят всю работу.
Вся основная работа выполняется перегруженным оператором "()" класса "accept_thread", а если быть более точным, то этот оператор выполняет следующие действия:
Или более кратко -- принимает соединение, обрабатывает запрос, возвращает ответ и закрывает соединение.
Класс "client" отвечает за передачу запроса на сервер. Его определение и реализацию вы найдете в файлах:
Метод "send_request" выполняет следующие действия:
Данной статьей я представил вашему вниманию пример платформы для создания сервера приложений на языке программирования C++. Надеюсь мои пояснения были достаточно понятными для того, чтобы вы смогли использовать приведенный здесь код в своих разработках.
В этом архиве находится весь исходный код, который упоминается в статье:
Вы можете разархивировать и собрать работающий пример следующими командами:
prompt$ gunzip app_server_c++.tar.gz prompt$ tar -xf app_server_c++.tar prompt$ cd app_server_c++ prompt$ make
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван
Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн,
Андрей Киселев, Игорь Яровинский, Юрий Прушинский
Со всеми предложениями, идеями и комментариями обращайтесь к Александру Куприну ([email protected]). Убедительная просьба: указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в рассылке.
Сайт рассылки: http://gazette.linux.ru.net
Эту статью
можно взять здесь: http://gazette.linux.ru.net/lg79/tougher.html
Архивы
выпусков можно взять здесь: http://gazette.linux.ru.net/archive/