Платформа для создания серверов приложений на C++

Автор: (c) 2002 Rob Tougher
Перевод: (c) 2003 Андрей Киселев


1. Введение
2. Работа с платформой
2.1 Установка Expat
2.2 Основные классы C++
2.3 Пример реализации простых сервера и клиента.
3. Внутреннее устройство
3.1 XML
3.2 Сеть
3.3 Потоки
3.4 Класс server
3.5 Класс client
4. Заключение
a. Исходный код
b. Ссылки

1. Введение

Сервер приложений -- это программа, предоставляющая другим программам некоторый перечень "услуг", как правило через сетевые соединения.

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

В данной статье я хочу представить код, который может служить каркасом для создания серверов приложений, написанных на C++. Сначала я расскажу о том, как работать с таким "каркасом", а затем, для любознательных, постараюсь объяснить внутреннее устройство и принципы работы. Надеюсь, что по прочтении данной статьи вы будете достаточно ясно понимать принципы работы этого каркаса, служащего платформой для создания клиент-серверных приложений на C++, и сможете использовать ее код в своих разработках.

2. Работа с платформой

2.1 Установка Expat

Прежде всего, для того, чтобы попробовать поработать с кодом, вам потребуется установить Expat XML Parser (Библиотека грамматического разбора предложений языка разметки XML). Код предлагаемой вашему вниманию платформы "заворачивает" в XML данные, циркулирующие между серверами и клиентами, а библиотека Expat используется для грамматического разбора XML. Сразу замечу, что примеры, приводимые в данной статье, тестировались с Expat версии 1.95.2.

2.2 Основные классы C++

Платформа предоставляет в распоряжение программиста четыре основных класса:

Класс request -- это запрос, передающийся от клиента к серверу. Класс имеет свойство "name" и набор параметров запроса -- "params". Для большей простоты можно представить себе передачу запроса как вызов метода, где "name" -- имя метода, а "params" -- набор передаваемых ему аргументов.

Класс reply представляет собой ответ сервера на полученный запрос. Он имеет свойство "value" и список ошибок "errors". Для большей простоты ответ можно представить себе как значение, возвращаемое при вызове метода-запроса, где "value" -- фактическое возвращаемое значение, а "errors" -- список ошибок, возникших в процессе выполнения тела метода.

Класс server -- это "рабочая лошадка" платформы. Вы можете использовать его в качестве "родительского" при создании своих собственных серверов. Основной интерес для вас будут представлять два метода, реализуемые данным классом -- "handle_request" и "run". Метод "run" запускает сервер и начинает принимать соединения с клиентами. Метод "handle_request" отвечает за обработку всех поступивших от клиентов запросов. При создании своего сервера метод "handle_request" необходимо "перекрыть" в "дочернем" классе.

И наконец -- класс client, который отвечает за передачу запроса на сервер. При создании экземпляра класса, конструктору передаются сетевое имя сервера и номер порта, а затем, с помощью метода "send_request" можно отправлять на сервер свои запросы.

2.3 Пример реализации простых сервера и клиента

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

В программе simple_quoter_server.cpp определяется класс "simple_quoter_server" -- наследник от xmlrpc::server. В нем перекрывается метод "handle_request", который вызывается всякий раз, когда от клиента поступает запрос. Если запрос содержит параметр "ticker" со значением "RHAT", то возвращается ответ со свойством "value", содержащим текущую цену этого продукта. Если возникают какие либо ошибки, то они заносятся в список "errors".

В программе simple_quoter_client.cpp просто создается экземпляр класса xmlrpc::client, создается и заполняется запрос и затем, вызовом метода "send_request", запрос передается на сервер.

3. Внутреннее устройство.

3.1 XML

В предлагаемом вам примере, для оформления сообщений, которыми обмениваются клиенты и серверы, используется язык разметки 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".

3.2 Сеть

Обсуждаемый пример платформы использует для передачи данных по сети три класса:

Описание этих классов вы найдете в январском выпуске "Linux Gazette". Названия классов я изменил, но реализация осталась прежней.

3.3 Потоки

Класс "server", рассказ о котором пойдет ниже, обслуживает запросы от клиентов в отдельных потоках выполнения [Их часто называют "в лоб": "треды". -- Прим. ред.]. Для работы с потоками платформа предоставляет следующие классы:

Класс "thread" представляет собой отдельный поток. Концепцию класса "thread_group" я заимствовал из Boost.Threads Library. По сути: если вам требуется создать отдельный поток -- используйте класс "thread", группу потоков -- класс "thread_group".

Рекомендую при первом удобном случае посетить сайт Boost. Boost -- это группа энтузиастов, которая занимается разработкой кросс-платформенных библиотек на языке C++. Особо подчеркну, что некоторые члены этой группы входят в состав Комитета по Стандартизации Языка Программирования C++ (C++ Standards Committee), а это означает, что библиотеки, которые вы найдете на этом сайте, будут отличаться высоким качеством.

3.4 Класс "server"

Определение и реализация класса "server" находится в следующих файлах:

Обратите внимание на определение класса "accept_thread" в начале *.cpp файла. Вы еще не забыли, что я назвал класс "server" "рабочей лошадкой"? Хочу признаться в том, что несколько погрешил против истины. Вся работа класса "server" заключается в создании массива объектов класса "accept_thread" и ожидании в бездействии пока они выполнят всю работу.

Вся основная работа выполняется перегруженным оператором "()" класса "accept_thread", а если быть более точным, то этот оператор выполняет следующие действия:

  1. принимает запросы на соединение
  2. считывает данные из соединения до тех пор, пока не будет получен признак окончания передачи
  3. записывает данные в объект класса "request"
  4. для обработки запроса вызывает метод "handle_request". А поскольку этот метод является виртуальным, то в случае, если вы перекрыли его в своем классе-потомке, будет вызван ваш код (пример полиморфизма в действии прим. перев.).
  5. получает XML-строку с ответом от метода "handle_request"
  6. передает ответ обратно клиенту, добавляя к нему признак завершения передачи

Или более кратко -- принимает соединение, обрабатывает запрос, возвращает ответ и закрывает соединение.

3.5 Класс "client"

Класс "client" отвечает за передачу запроса на сервер. Его определение и реализацию вы найдете в файлах:

Метод "send_request" выполняет следующие действия:

  1. устанавливает соединение с сервером
  2. передает XML-запрос, завершая его признаком конца передачи
  3. считывает данные из соединения до тех пор, пока не будет обнаружен признак конца передачи
  4. передает полученную XML-строку объекту класса "reply"
  5. возвращает объект "reply" в вызвавшую функцию

4. Заключение

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

a. Исходный код

В этом архиве находится весь исходный код, который упоминается в статье:

Вы можете разархивировать и собрать работающий пример следующими командами:

prompt$ gunzip app_server_c++.tar.gz
prompt$ tar -xf app_server_c++.tar
prompt$ cd app_server_c++
prompt$ make

b. Ссылки


Rob Tougher

Роб -- пишущий на C++ программист из Нью-Йорка. В свободное от работы время его можно найти прогуливающимся по пляжу со своей девушкой Николь (Nicole) и их собакой Холли (Halley).
Copyright (c) 2002, Rob Tougher.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 79 of Linux Gazette, June 2002

Вернуться на главную страницу