![]() |
Технология Клиент-Сервер 2004'3 |
||||||
|
В статье описан процесс разработки простого клиент-серверного приложения с использованием автоматно-ориентированного подхода. В качестве инструмента для моделирования конечных автоматов применяется, разработанный при участии авторов, программный пакет с открытым кодом UniMod, который основан на SWITCH-технологии и позволяет создавать схемы связей и графы переходов автоматов, используя UML-нотацию диаграмм классов и диаграмм состояний соответственно.
Опишем процесс разработки простого клиент-серверного приложения на примере разработки системы мгновенного обмена сообщениями между любым количеством пользователей (простого аналога ICQ).
Система состоит из сервера сообщений и однотипных клиентских приложений. После запуска клиентское приложение присоединяется к серверу сообщений. Затем оно может получить список пользователей, уже присоединенных к серверу, и обмениваться сообщениями с этими пользователями. При выходе какого-либо пользователя из системы остальные пользователи должны быть уведомлены об этом. При завершении работы сервера все подсоединенные пользователи должны отключится от него.
При взаимодействии между серверным и клиентским приложениями в качестве транспортного протокола используется протокол TCP, который позволяет пересылать массивы байт. Для обмена сообщениями на более высоком (прикладном) уровне необходимо создать другой протокол, что является одной из составных частей процесса разработки. Будем говорить, что клиентское и серверное приложения обмениваются сообщениями, в то время как пользователи системы – репликами.
В соответствии с известными методологиями объектно-ориентированного проектирования [1] первым шагом при создании системы является описание требований, предъявляемых к системе, и вариантов ее использования (use cases).
Перечислим требования к системе. Должна быть обеспечена возможность:
Варианты использования можно описывать как с помощью UML-диаграмм соответствующего типа, так и в текстовом виде [2]. Авторы применили второй подход:
Следующим шагом, в соответствии с известными методологиями, является создание модели предметной области в виде UML-диаграммы классов.
Модель предметной области системы приведена на рисунках 1 и 2.
Рисунок 1. Диаграмма классов. Часть 1.
Диаграмма на рисунке 1 содержит две группы классов:
Рисунок 2. Диаграмма классов. Часть 2.
Диаграмма на рисунке 2 содержит еще две группы классов:
Опишем каждую из этих групп с учетом связей на приведенных выше диаграммах.
Приведем описание классов, предназначенных для передачи данных:
Тип сообщения |
Инициатор сообщения | Описание |
---|---|---|
LOGIN | Клиент | Запрос входа в систему. Сообщение должно содержать имя и пароль пользователя |
LOGIN | Сервер | Подтверждение входа |
LOGOUT | Клиент | Сообщение о выходе из системы |
LOGOUT | Сервер | Отказ во входе в систему либо уведомление о завершении работы сервера |
REPLICA | Клиент | Посылка реплики пользователем другому пользователю |
REPLICA | Сервер | Пересылка сервером реплики адресату |
USERS | Клиент | Запрос списка присоединенных пользователей |
USERS | Сервер | Сообщение, содержащее список присоединенных пользователей |
Группа вспомогательных классов содержит:
Запуск серверного и клиентского приложений обеспечивают классы Server и Client соответственно.
Классы, реализующие функциональность подсистем:
После создания модели предметной области системы воспользуемся автоматно-ориентированным подходом, который в явном виде не применяется в известных методологиях.
В работе [4] предложен метод проектирования событийных объектно-ориентированных программ с явным выделением состояний, названный «SWITCH-технологией». Особенность этого подхода состоит в том, что поведение в таких программах описывается с помощью графов переходов структурных конечных автоматов с нотацией, предложенной в работе [5].
SWITCH-технология определяет для каждого автомата два типа диаграмм (схему связей и граф переходов) и их операционную семантику. При наличии нескольких автоматов, кроме того, строится схема их взаимодействия. SWITCH-технология задает свою нотацию диаграмм. Предлагается, сохранив автоматный подход, использовать UML-нотацию при построении диаграмм в рамках SWITCH-технологии. При этом с применением нотации диаграмм классов языка UML строятся схемы связей автоматов, определяющих их интерфейс, а графы переходов строятся с помощью UML-нотации диаграммы состояний.
Применение автоматно-ориентированного подхода для моделирования систем состоит в следующем:
Рисунок 3. Схема связей серверного автомата A1
Программный пакет с открытым кодом UniMod (http://unimod.sf.net), созданный компанией eVelopers (http://www.evelopers.com), обеспечивает разработку и выполнение автоматно-ориентированных программ. Он выполнен как встраиваемый модуль (plug-in) для платформы Eclipse (http://www.eclipse.org) и позволяет создавать и редактировать UML-диаграммы классов и состояний, которые соответствуют схеме связей и графу переходов.
Пакет UniMod, после создания диаграмм, предоставляет возможность:
Проектирование программ с использованием пакета UniMod состоит в следующем:
В проекте используется язык Java, поэтому источникам событий и объектам управления соответствуют классы. Для этого языка в пакете UniMod реализован интерпретатор XML-описаний, которые пакет строит на основе указанных диаграмм. При запуске программы интерпретатор загружает в оперативную память XML-описание и создает экземпляры источников событий и объектов управления. В процессе работы источники формируют события и направляют их интерпретатору, который обрабатывает события в соответствии с логикой, описываемой диаграммой состояний. При этом он вызывает методы объектов управления, реализующие входные переменные и выходные воздействия. Все действия, выполняемые интерпретатором, заносятся в протокол (лог), в котором отображаются также изменения состояний автомата.
Далее, в соответствии с описанным выше автоматно-ориентированным подходом, спроектируем серверную и клиентскую части разрабатываемого приложения, используя пакет UniMod.
Схема связей автомата, реализующего серверное приложение, созданная с помощью пакета UniMod, приведена на рисунке 3. Она построена следующим образом:
Событие | Описание | Аргументы (выходные данные) в формате имя:тип |
---|---|---|
ConnectorEventProvider | ||
e21 | Запрос входа в систему | MESSAGE: Message |
e22 | Уведомление о выходе из системы | MESSAGE: Message |
e23 | Получен текст | MESSAGE: Message |
e24 | Запрос на получение списка пользователей | USER: User |
Keyboard | ||
e100 | Введена строка с клавиатуры | INPUT: String |
Рисунок 4. Диаграмма состояний серверного автомата.
Для реализации входных переменных и выходных воздействий необходимо знать их функциональность и данные, которые они получают или заносят в общий поток данных [6]. В данной работе этот поток управляется диаграммой состояний. При этом отметим, что в дальнейшем будем различать входные переменные и входные данные, выходные воздействия и выходные данные. Первые из них предназначены для управления, а вторые – для описания потока данных.
Таблица 3. Входные переменные и выходные воздействия серверного приложения.
Воздействия | Описание | Входные данные в формате имя:тип |
Выходные данные в формате имя:тип |
---|---|---|---|
ServerConnector (o1) | |||
z1 | Послать сообщение, подтверждающее разрешение входа в систему | MESSAGE: Message | |
z2 | Послать сообщение, запрещающее вход в систему | MESSAGE: MessageREASON: String | |
z3 | Переслать текст всем присоединенным пользователям | MESSAGE: Message | |
z4 | Послать список пользователей инициатору сообщения | MESSAGE: Message | |
z5 | Послать список пользователей всем присоединенным пользователям | ||
z6 | Закрыть соединение с клиентом | MESSAGE: Message | |
UserDatabase (o3) | |||
x1 | Возвращает true в случае успешной авторизации пользователя, и false в противном случае. Если авторизация не удалась, также возвращает причину ошибки в виде выходного значения. | MESSAGE: Message | REASON: String |
Поясним, как выполняется управление потоком данных. Модель поведения серверного приложения является событийной. Поэтому первоначальный источник данных – событие. Далее потребителями и поставщиками данных могут быть только методы объектов управления, соответствующие входным переменным и выходным воздействиям. Поток начинается с данных, пришедших с событием (выходные данные события), и завершается данными последнего воздействия (входные данные воздействия), выполненного автоматом при обработке события.
Общее правило для формирования корректного потока данных может быть сформулировать следующим образом: если для каждого перехода сформировать последовательность из события, входных переменных и выходных воздействий, записанных на переходе, то множество входных данных каждого элемента последовательности должно включаться в объединение множеств выходных данных всех предшествующих элементов. Это правило является ограничением при проектировании.
Отметим, что для класса A1 код на языке Java отсутствует, но для него генерируется XML-описание, которое в дальнейшем интерпретируется.
В таблице 2 приведены описания событий, а в таблице 3 – описания входных переменных и выходных воздействий серверного приложения. Отметим, что в колонках, соответствующих входным и выходным данным, в качестве типов данных используются стандартный Java-класс String и контейнеры данных Message, User, определенные на диаграмме классов (рисунок 1).
Применяя эти обозначения, построим диаграмму состояний для автомата A1 (рисунок 4). При этом звездочка на диаграмме означает «любое событие».
Можно показать, что все переходы на этой диаграмме удовлетворяют правилу построения потока данных, сформулированного выше. Например, для дуги, помеченной выражением e21[!o3.x1]/o1.z2,o1.z6, это правило справедливо. Это иллюстрирует таблица 4.
Таблица 4.
Действие над потоком данных |
Содержимое потокаданных после выполнения действия |
---|---|
Событие e21 поставляет в поток значение MESSAGE:Message | MESSAGE:Message |
Входное воздействие o3.x1 получает из потока значение MESSAGE:Message и поставляет REASON:Reason | MESSAGE:MessageREASON:Reason |
Выходное воздействие o1.z2 получает из потока значение MESSAGE:Message и REASON:Reason | MESSAGE:MessageREASON:Reason |
Выходное воздействие o1.z2 получает из потока значение MESSAGE:Message | MESSAGE:MessageREASON:Reason |
Отметим, что автомат A1 является формальным описанием протокола взаимодействия клиента и сервера. Он имеет три состояния (рисунок 4), из которых только одно рабочее.
Если бы разрабатывался протокол с большим числом рабочих состояний, то пришлось бы на серверной стороне иметь по одному экземпляру автомата для каждого присоединенного клиента. Это объясняется тем, что в любой момент времени каждый из присоединенных клиентов может находиться в состоянии, отличном от состояния другого клиента. При этом можно говорить не об отдельном экземпляре автомата для каждого присоединенного клиента, а об отдельной конфигурации автомата, определяемой состоянием, в котором автомат в данный момент находится. В этом случае при появлении события от клиента серверному автомату помимо этого события передается также и состояние клиента.
Рисунок 5. Запуск серверного приложения из среды разработки
С помощью пакета UniMod на основе диаграмм, приведенных на рисунках 3 и 4, можно автоматически построить XML-описание автомата A1 (Листинг 1), которое в дальнейшем будет передано интерпретатору.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE model PUBLIC "-//eVelopers Corp.//DTD State machine model V1.0//EN" "http://www.evelopers.com/dtd/unimod/statemachine.dtd"> <model name="A2_model"> <controlledObject name="o1" class="com.evelopers.unimod.samples.messengersa.server.co.ServerConnector"/> <controlledObject name="o3" class="com.evelopers.unimod.samples.messengersa.server.co.UserDatabase"/> <eventProvider name="p1" class="com.evelopers.unimod.samples.messengersa.server.ep.ConnectorEventProvider"> <association targetRef="A1"/> </eventProvider> <eventProvider name="p2" class="com.evelopers.unimod.adapter.standalone.provider.Keyboard"> <association targetRef="A1"/> </eventProvider> <rootStateMachine> <stateMachineRef name="A1"/> </rootStateMachine> <stateMachine name="A1"> <association targetRef="o1" supplierRole="o1"/> <association targetRef="o3" supplierRole="o3"/> <state name="TOP" type="NORMAL"> <state name="Ready" type="NORMAL"/> <state name="EndState1" type="FINAL"/> <state name="StartState1" type="INITIAL"/> </state> <transition name="" sourceRef="Ready" targetRef="Ready" event="e22"> <outputAction ident="o1.z6"/> <outputAction ident="o1.z5"/> </transition> <transition name="" sourceRef="Ready" targetRef="Ready" event="e21" guard="!o3.x1"> <outputAction ident="o1.z2"/> <outputAction ident="o1.z6"/> </transition> <transition name="" sourceRef="Ready" targetRef="EndState1" event="e100"/> <transition name="" sourceRef="Ready" targetRef="Ready" event="e23"> <outputAction ident="o1.z3"/> </transition> <transition name="" sourceRef="Ready" targetRef="Ready" event="e24"> <outputAction ident="o1.z4"/> </transition> <transition name="" sourceRef="Ready" targetRef="Ready" event="e21" guard="o3.x1"> <outputAction ident="o1.z1"/> <outputAction ident="o1.z5"/> </transition> <transition name="" sourceRef="StartState1" targetRef="Ready"/> </stateMachine> </model> |
Это описание изоморфно указанным диаграммам. В нем сначала описаны объекты управления, затем источники событий, после этого имя головного автомата (в данном случае – A1), а в конце приведено описание диаграммы состояний автомата A1.
Рисунок 6. Пользовательский интерфейс клиентского приложения.
Этот листинг является описанием поведения серверного приложения. Поэтому о нем можно говорить как об автоматной программе, которой поставляются события от источников событий и из которой вызываются методы объектов управления. В силу изоморфизма и благодаря автоматической генерации XML-описания, диаграммы, изображенные на рисунках 3 и 4, можно считать автоматной графической программой.
Если Java-классы, соответствующие источникам событий и объектам управления, уже реализованы, то существует возможность «запустить» диаграмму состояний прямо из среды разработки. На рисунке 5 показана среда разработки, в которой запущено серверное приложение.
Рисунок 7. Схема связей клиентского автомата A2.
В нижней части этого рисунка показан лог работы автомата A1. Протокол имеет следующий формат:
[StateMachine.ActiveState % StateBeingProcessed] Message |
В этом формате применяются следующие обозначения:
Из анализа протокола следует, что после запуска интерпретатор передал автомату событие e0, которое уведомляет о начале работы. После этого автомат перешел в состояние Ready. Это активное состояние автоматически выделяется на диаграмме состояний рамкой.
Далее описан процесс проектирования клиентского приложения.
Окно пользовательского интерфейса представлено на рисунке 6. Ссылками показаны события, порождаемые кнопками Send и Connect.
Схема связей автомата A2, реализующего поведение клиентского приложения, представлена на рисунке 7. Она построена следующим образом:
В таблице 4 приведено описание событий, воздействующих на автомат A2, а в таблице 5 – описание входных переменных и выходных воздействий, реализуемых объектами управления, связанными с этим автоматом. Также как и в описании серверного приложения, в колонках, описывающих входные и выходные данные, в качестве типов данных используются стандартный Java класс String и контейнеры данных Replica, User, определенные на рисунке 1.
На рисунке 8 показана диаграмма состояний автомата A2.
Таблица 4. События для клиентского приложения.
Событие | Описание | Аргументы (выходные данные) в формате имя:тип |
---|---|---|
ConnectorEventProvider | ||
e21 | Получено сообщение, разрешающее вход в систему | |
e22 | Получено сообщение, запрещающее вход в систему или уведомление о выходе сервера из системы | REASON: String |
e23 | Получен текст | REPLICA: Replica |
e24 | Получен список пользователей | USERS: User [] |
ScreenEventProvider | ||
e1 | Нажата кнопка Send | REPLICA: String |
e2 | Нажата кнопка Connect | |
e100 | Запрос на закрытие окна |
Листинг 2 содержит XML-описание диаграмм, изображенных на рисунках 7 и 8. Данное описание изоморфно указанным диаграммам.
Таблица 5. Входные переменные и выходные воздействия клиентского приложения.
Действия | Описание | Входные данные в формате имя:тип | Выходные данные в формате имя:тип |
---|---|---|---|
ClientConnector (o1) | |||
z1 | Присоединится к серверу | ||
z2 | Отсоединится от сервера | ||
z3 | Послать текст всем пользователям | REPLICA: String | |
Screen (o2) | |||
z3 | Обновить список подключенных пользователей | USERS: User [] | |
z4 | Отобразить текст сообщения | REPLICA: Replica | |
z5 | Изменить надпись Connect на Disconnect на кнопке. Обновить строку состояния окна | ||
z6 | Изменить надпись Disconnect на Connect на кнопке. Обновить строку состояния окна | REASON: String | |
Config (o3) | |||
x1 | Выполняет конфигурирование клиентского приложения. Возвращает true,если конфигурирование произведено успешно и false в противном случае |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE model PUBLIC "-//eVelopers Corp.//DTD State machine model V1.0//EN" "http://www.evelopers.com/dtd/unimod/statemachine.dtd"> <model name="A1_model"> <controlledObject name="o1" class="com.evelopers.unimod.samples.messengersa.client.co.ClientConnector"/> <controlledObject name="o2" class="com.evelopers.unimod.samples.messengersa.client.co.Screen"/> <controlledObject name="o3" class="com.evelopers.unimod.samples.messengersa.client.co.Config"/> <eventProvider name="p1" class="com.evelopers.unimod.samples.messengersa.client.ep.ConnectorEventProvider"> <association targetRef="A2"/> </eventProvider> <eventProvider name="p2" class="com.evelopers.unimod.samples.messengersa.client.ep.ScreenEventProvider"> <association targetRef="A2"/> </eventProvider> <rootStateMachine> <stateMachineRef name="A2"/> </rootStateMachine> <stateMachine name="A2"> <association targetRef="o1" supplierRole="o1"/> <association targetRef="o2" supplierRole="o2"/> <association targetRef="o3" supplierRole="o3"/> <state name="TOP" type="NORMAL"> <state name="Disconnected" type="NORMAL"/> <state name="Connected" type="NORMAL"/> <state name="Start" type="INITIAL"/> <state name="Final" type="FINAL"/> <state name="s1" type="FINAL"/> </state> <transition name="" sourceRef="Disconnected" targetRef="Final" event="e100"/> <transition name="" sourceRef="Disconnected" targetRef="Disconnected" event="e1"> <outputAction ident="o1.z1"/> <outputAction ident="o1.z3"/> </transition> <transition name="" sourceRef="Disconnected" targetRef="Disconnected" event="e2"> <outputAction ident="o1.z1"/> </transition> <transition name="" sourceRef="Disconnected" targetRef="Connected" event="e21"> <outputAction ident="o2.z5"/> </transition> <transition name="" sourceRef="Connected" targetRef="Final" event="e100"/> <transition name="" sourceRef="Connected" targetRef="Connected" event="e24"> <outputAction ident="o2.z3"/> </transition> <transition name="" sourceRef="Connected" targetRef="Connected" event="e23"> <outputAction ident="o2.z4"/> </transition> <transition sourceRef="Connected" targetRef="Connected" event="e1"> <outputAction ident="o1.z3"/> </transition> <transition name="" sourceRef="Connected" targetRef="Disconnected" event="e22"> <outputAction ident="o2.z6"/> </transition> <transition name="" sourceRef="Connected" targetRef="Disconnected" event="e2"> <outputAction ident="o1.z2"/> <outputAction ident="o2.z6"/> </transition> <transition name="" sourceRef="Start" targetRef="s1" guard="!o3.x1"/> <transition name="" sourceRef="Start" targetRef="Disconnected" guard="o3.x1"> <outputAction ident="o2.z6"/> </transition> </stateMachine> </model> |
На рисунке 9 показана среда разработки с запущенным клиентским приложением. Из анализа протокола следует, что после запуска приложение перешло в состояние Disconnected. Это состояние автоматически выделено рамкой на диаграмме состояний.
На рисунке 10 показано два запущенных из среды разработки клиентских приложения и протокол серверного приложения. Клиентские приложения присоединены к серверу и обмениваются сообщениями. Из анализа серверного протокола следует, что последним было обработано событие e23 – получен текст. В протоколе также отображено, что при обработке этого события выполнено выходное воздействие o1.z3 – пересылка текста присоединенным пользователям. В окнах клиентских приложений видны реплики, которыми обменивались пользователи, пересылая сообщения друг другу через сервер. На диаграмме состояний также видно, что клиентские приложения находятся в состоянии Connected – оно выделено рамкой.
Рисунок 8. Диаграмма состояний клиентского автомата A2.
Опишем, как модифицируется система при необходимости. Предположим, что весь код, реализующий источники событий и объекты управления, уже написан и отлажен. Предположим также, что появилась необходимость внести следующие изменения:
Проанализировав таблицы 5 и 6, можно сделать вывод, что написанный код изменять не следует, а требуется изменить лишь диаграмму состояний автомата A2. При этом в состоянии Disconnected необходимо добавить петлю, помеченную символами e1/o1.z1,o1.z3 (при получении события «послать сообщение» необходимо присоединиться к серверу и послать сообщение), и убрать переход из состояния Connected в финальное состояние (запретить завершение работы приложения при получении события «запрос на закрытие окна»).
На рисунке 11 представлена модифицированная диаграмма состояний клиентского автомата A2.
Рисунок 9. Клиентское приложение запущено из среды разработки.
После этого достаточно сгенерировать новое XML-описание автомата A2, и система готова к работе.
Рисунок 10. Одна из рабочих ситуаций с двумя запущенными клиентскими приложениями.
Рисунок 11. Преобразованная диаграмма состояний автомата A2.
В работе приведен пример построения простого клиент-серверного приложения на основе автоматного подхода. При этом основой для разработки такого приложения является описание протокола взаимодействия между клиентами и сервером. С использованием этого протокола строятся диаграммы состояний рассматриваемых приложений, а также описываются потоки данных.
Применяемый подход базируется на совместном использовании SWITCH-технологии и UML-диаграмм. Диаграммы создаются в свободно распространяемом пакете для автоматно-ориентированного программирования UniMod.
На основе анализа предметной области строится диаграмма классов системы, выделяются объекты и управляющие ими автоматы.
Схема связей каждого автомата изображается с помощью нотации диаграммы классов, а его граф переходов – в виде диаграммы состояний. С помощью пакета UniMod эти диаграммы преобразуются в XML-описание, которое интерпретируется после «ручной реализации» источников событий и объектов управления.
Правильность работы приложения демонстрируется с помощью протоколирования, выполняемого в автоматной терминологии, и подсветки активных состояний на диаграмме состояний, осуществляемой автоматически.
Как показано в разделе «Обеспечение модификации системы», при наличии библиотеки источников событий и объектов управления для некоторой предметной области, можно обойтись без ручного программирования.
Исходные тексты описанного клиент-серверного приложения и пакет для автоматно-ориентированного программирования UniMod доступны по адресу http://unimod.sf.net/.
В заключении отметим, что рассмотренный пример весьма прост, однако у авторов есть основания предполагать, что приведенный подход может быть распространен и на более сложные системы.
Copyright © 1994-2016 ООО "К-Пресс"