Многоуровневые открытые сетевые протоколы и блочная декомпозиция
Современные телекоммуникационные системы не просто должны качественно выполнять свои функции. Им нужно также быть совместимыми с другими подобными системами. Это важно по следующим соображениям. Во-первых, тогда можно пользоваться технологиями, реализованными другими производителями, собирая систему из готовых аппаратных и программных компонент, а самостоятельно реализуя лишь уникальную, специфическую функциональность. Это существенно экономит ресурсы разработки. Во-вторых, телекоммуникационные системы в большинстве случаев являются частями глобальной мировой телекоммуникационной сети: кому, например, нужна телефонная станция, которая хорошо обслуживает абонентов одного поселка, но не позволяет им позвонить в близлежащий город, за границу и т. д.?
Достичь легкого использования готовых компонент, а также обеспечить открытость и совместимость позволяет следование международным телекоммуникационным стандартам, которые развиваются уже не одно десятилетие такими комитетами, как ITU, ISO, ESTI и др. Большую роль в телекоммуникационных стандартах играет концепция многоуровневых открытых сетевых протоколов, стандартизованная международным комитетом ISO в модели ISO/OSI.
В рамках данных лекций не будет рассматриваться содержательный аспект этой концепции, а также конкретные телекоммуникационные стандарты ISDN, ATM, GSM и т. д. Остановимся лишь на самой идее многоуровневого сетевого протокола, которая широко используется при проектировании программно-аппаратных телекоммуникационных систем.
В основе многоуровневой модели лежит разбиение сложной телекоммуникационной функциональности на уровни или "слои" - чем выше, тем абстрактнее (рис.10.2).
Рис. 10.2. Уровни многоуровневой модели
Нижний уровень обслуживает верхний, предоставляя ему нужные для работы примитивы и скрывая от него логику обработки этих примитивов. Как правило, через уровни "прыгать" не принято (например, уровню N+2 нельзя напрямую обратиться к уровню N ), хотя в некоторых телекоммуникационных стандартах такое встречается. Внутри себя каждый из уровней может содержать функциональные сущности ("листья декомпозиции") и подуровни (а те, в свою очередь, содержат другие подуровни и/или функциональные сущности) см.рис.10.3. На этом рисунке показано, что уровень N+1 содержит три функциональных сущности, уровень N - два подуровня. Декомпозиция "в глубину" может быть продолжена аналогичным образом. Еще из рис.10.3 видно, что все соединения между уровнями, подуровнями и функциональными сущностями происходят через точки подключения, в которых определены интерфейсы взаимодействия.
Рис. 10.3. Уровни, подуровни и функциональные сущности многоуровневой модели
Будем называть такую декомпозицию блочной. Она отличается от других видов декомпозиции, рассмотренных при изучении UML, - например, агрегирования - следующим:
- целое полностью скрывает свои части от окружения - сами части и их связи наружу не видны;
- связи, идущие к целому извне, "протаскиваются" внутрь, через декомпозиционную иерархию, к его частям (как будет показано ниже, в UML, для этого используются транзитные порты и делегирующие соединители ).
Иерархическую блочную декомпозицию можно попробовать промоделировать цепочкой композиций классов UML (напомню, что композиция - это "сильное" агрегирование). Но нет способа задать для экземпляров классов-частей отношения, которые действуют только внутри их объекта-агрегата (такие связи можно было бы назвать локальными ассоциациями). И уж тем более остается открытым вопрос с "протаскиванием" связей через иерархию декомпозиции.
Телекоммуникационные стандарты описывают различные сетевые интерфейсы, а не просто функциональность телекоммуникационных систем. Все сказанное выше применяется для этой цели следующим образом. На 10.4 показано, что на каждой из взаимодействующих сетевых сторон определяется по одному "бутерброду" из уровней. На каждом из уровней между этими сетевыми сторонами определяются свои протоколы, и нижележащие уровни служат для этих протоколов транспортной средой. Самый нижний уровень является физическим и "гоняет" по проводам электрические импульсы. Выше появляются биты, пакеты и т. д. Общение двух уровней через сеть называется peer-to-peer взаимодействием.
Рис. 10.4. Peer-to-peer взаимодействие уровней
Сообщения двух равных (peer) уровней передаются по сети не "напрямую", а "спускаются вниз", по стеку протокола одной сетевой стороны, "обрастая" дополнительной служебной информацией, а также вспомогательными сообщениями (например, для установки различных низкоуровневых каналов, гарантирующих надежность передачи). Верхнеуровневое сообщение может быть также разбито на части и передаваться по сети этими "кусочками". На принимающей стороне эти "кусочки" должны быть вновь собраны в исходное сообщение, а само оно "поднято наверх".
Уровни, подуровни и функциональные сущности связываются друг с другом через сервисные точки (access points), в которых определяются двусторонние интерфейсы обмена сообщениями. К сервисным точкам ведут каналы снаружи блоков и от их элементов, т. е. изнутри. Ниже мы увидим, что сервисные точки моделируются портами UML 2.0.
Итак, блочная декомпозиция является важнейшим принципом моделирования сложных телекоммуникационных систем.
Композитные компоненты
В UML 2.0. есть композитные компоненты, которые можно изображать на специальных диаграммах композитных структур и которые, по сравнению с обычными UML -компонентами, изображаемыми на диаграммах компонент, имеют порты и аналоги каналов, а также могут иметь внутреннюю структуру, т. е. поддерживают блочную декомпозицию.
Блочная декомпозиция в UML усложнена поддержкой типов. Во-первых, композитные компоненты являются типами компонент, а во-вторых, внутри себя они состоят из частей, которые принадлежат к другим типам компонент. Давайте посмотрим, чем блочная декомпозиция типов отличается от экземплярной блочной декомпозиции.
Пусть создается сеть телефонных станций из трех штук для различных сельских поселков одного района. Предположим, что каждая из этих станций - особенная. Эти станции рассчитаны на разное количество абонентов (поселки могут существенно различаться численностью населения), состоят из разного оборудования, используют различное программное обеспечение и пр. Вся система разбивается на три подсистемы, каждая из них - на другие подсистемы и т. д. Получается картинка в стиле рис.10.3. Это - экземплярная блочная декомпозиция, поскольку на части разбиваются реально существующие в системе экземпляры.
Пусть теперь создается сеть из двадцати телефонных станций, для двадцати поселков. Использовать в каждом поселке абсолютно уникальную разработку - очень накладно. Появляется несколько типов станций, которыми и "покрываются" особенности, имеющиеся в различных населенных пунктах. Каждый тип станции внутри себя устроен одинаково.
Экземплярная блочная декомпозиция не подходит для моделирования структуры сложных СРВ, поскольку при этом часто возникает потребность определять множество типовых узлов и на их основе конструировать другие типы узлов. Например, типовая телефонная станция может содержать несколько однотипных пользовательских компьютеров для рабочих мест операторов и один сервер. Типовой пользовательский компьютер (тип компоненты "ТиповаяРабочаяСтанция"), к примеру, должен включать в себя 15-дюймовый монитор, процессор по быстродействию не ниже Pentium IV 1,6 Гц, сетевую карту, а в некоторых случаях еще и CD-устройство. Все это изображено на рис.10.5, выполненном в нотации диаграмм композитных структур UML 2.0.
В этом примере на верхнем уровне блочной декомпозиции можно увидеть два типа компонент - "ТиповаяРабочаяСтанция" и "ТиповойСервер". Они состоят из частей, среди которых "МониторТРС" и "МониторТС" имеют одинаковый тип - "15ДМонитор". Второй уровень представлен спецификацией компонентного типа "СистемныйБлокТРС", используемого в определении типа "ТиповаяРабочаяСтанция". Наконец, на третьем уровне представлен тип компоненты "МатеринскаяПлатаТРС", который используется при определении типа "ТиповаяРабочаяСтанция". (Дальше раскрывать не будем, ограничившись спецификацией портов и интерфейсов. Ведь где-то надо остановиться!) Все типы компонент, показанные на этом рисунке, - "ТиповаяРабочаяСтанция", "ТиповойСервер", "СистемныйБлокТРС", "МатеринскаяПлатаТРС" - являются композитными компонентами.
Не будем пока рассматривать многочисленные детали композитных компонент, а остановимся на следующем вопросе: чем являются их части с точки зрения UML?
Эти части называются ролями (roles) и уже многократно встречались нам - в диаграммах последовательности и коммуникаций, временных диаграммах, при изучении коопераций. Здесь эта конструкция будет, наконец, рассмотрена детально.
Роли компонент (далее - просто роли) обязательно имеют тип и служат "гнездами" для подстановки конкретных экземпляров своих типов компонент. Например, в гнездо "ПамятьТРС" можно подставить от 2 до 8 экземпляров типа "МикросхемыПамяти". А в безымянное гнездо в типе "СистемныйБлокТРС", имеющее тип "CD-устройство", можно подставить один экземпляр этого типа или ни одного.
Роль является промежуточной абстракцией между типом и экземпляром. Она похожа на тип, так как тоже определяет множество экземпляров. Она похожа на экземпляры, так как задает строго определенное количество однотипных экземпляров (и часто - ровно один, когда не указывается в квадратных скобках множественность).
Роль отличается от типа тем, что является контекстно-зависимым определением набора экземпляров. В самом деле, тип (класс, тип компоненты) определяет экземпляры, которые могут появиться практически в любом месте системы (естественно, в соответствии с правилами видимости). А экземпляры ролей могут появляться только в определенной композитной компоненте. В целом контекстной свободы у типа существенно больше, чем у роли.
Имя роли задается так:
<идентификатор1>: <идентификатор2>,
где <Идентификатор1> - это имя роли, а <Идентификатор2> - имя ее типа. Тот или иной идентификатор могут быть опущены.
Рис. 10.5. Пример блочной декомпозиции типов средствами UML
На рис.10.5 почти все роли не имеют имен, а содержат лишь указания на типы своих компонент - здесь этого оказалось достаточно. Даны имена только двум ролям - "МониторТРС: 15ДМонитор" в компоненте "ТиповаяРабочаяСтанция" и "СистемныйБлокТС" в компоненте "ТиповойСервер". В обоих этих компонентах задействованы узлы одинокого типа, поэтому естественно дать им разные имена. Еще я задал имя Pentium-процессору - Процессор ТРС, - чтобы подчеркнуть, что речь идет именно о процессоре.
Имена ролей в разных композитных компонентах могут совпадать, в том числе и для ролей одинаковых типов. Ведь композитная компонента является закрытым пространством имен. Однако, во избежание путаницы, так лучше не делать.
Далее, для простоты изложения, далее будем называть и композитные компоненты, и роли, из которых они состоят, просто компонентами. Надеюсь, что читатель не запутается и из контекста поймет, что означает очередная "компонента".
SADT является, по всей видимости, первой методологией, где был сформулирован и реализован принцип блочной декомпозиции. Его авторы считали, что при проектировании системы нужно поместить в модель всю информацию, которая нужна, "без купюр", но одновременно расположить ее в виде, доступном для восприятия и дальнейшей работы. Это достигалось через иерархическую декомпозицю - на одной диаграмме изображалось несколько блоков, каждый из которых далее раскрывался в следующую диаграмму и т. д. Декомпозиция поддерживалась с учетом различных особенностей нотации SADT . При этом на одной диаграмме предлагалось размещать примерно семь сущностей, что соответствует правилу "семь плюс/минус два", сформулированном еще в 1956 г оду (речь идет о том, что именно это количество единиц информации оптимально для одномоментного восприятия человеком). В SADT поддерживалась декомпозиция экземпляров блоков, типов там не было.
В 1970-х - 1990-х годах создавался и развивался язык SDL для моделирования телекоммуникационных систем. В этом языке использовался тот же принцип декомпозиции, что и в SADT, но к блокам добавились каналы, точки соединения, сообщения и прочие атрибуты, необходимые для телекоммуникаций. В дальнейшем, в версиях SDL-92 и SDL-2000 появились типы блоков, наследование и другие объектно-ориентированные черты. Также были унифицированы структурные сущности - изначально, кроме блоков в SDL входили системы, подсистемы, процессы, сервисы, а теперь там есть только агенты. Однако, эти последние новшества оказались данью моде, сделали язык более запутанным, громоздким и непонятным. В итоге, пройдя почти тридцатилетний путь развития, язык SDL уступил меcто UML.
В начале 1990-х годов появилась методология ROOM - объектно-ориентированный подход к моделированию систем реального времени. В рамках этого подхода был предложен способ для декомпозиции структуры сложных систем реального времени на основе типов и ролей, который впоследствии был использован в UML 2.0. Однако методология ROOM содержит существенно более богатые средства структурной декомпозиции, чем UML, - в частности, она включает поддержку полноценных каналов, а также сервисных соединений, широко используемых в моделях открытых многоуровневых сетевых протоколов.
Интерфейс
Это понятие уже рассматривалось выше, в контексте диаграмм компонент UML. Теперь оно будет изучено более детально. Интерфейс (interface) - это конструкция, которая позволяет компоненте, скрывая ее внутреннее устройство, предоставить вовне определенный способ обращения к своей функциональности. Компонента может сделать доступными через свой интерфейс следующие примитивы:
- операции - для синхронного взаимодействия ;
- переменные - опять-таки для синхронного взаимодействия;
- сообщения - для асинхронного взаимодействия.
Синхронное взаимодействие - одна компонента обратилась к другой, и пока та не ответит, обратившаяся ждет, не продолжает свою работу. Очевидно, что вызов операции - как раз синхронное взаимодействие, поскольку пока операция не выполнится, вызвавшая ее компонента не может продолжить свою работу.
С обращением к переменной - то же самое. Как правило, реализация обращений к переменной интерфейса компоненты происходит через служебные операции set (для установки значения) и get (для чтения значения), которые скрыты от пользователя интерфейса.
Асинхронное взаимодействие - компонента послала запрос и, не дожидаясь ответа на него, продолжила свою работу. Классическим способом реализации асинхронного взаимодействия является посылка сообщения. Компонента реализует интерфейс с сообщениями, когда умеет их обрабатывать.
Рассмотрим пример. Ниже представлен интерфейс Connect, который могут реализовывать компоненты "Концентратор1" и "Концентартор2" из примера на рис.10.1, а использовать - компоненты "Абонент1" и "Абонент2". Последние с его помощью устанавливают связь со станцией в случае исходящего вызова (операция EstablishConnect), устанавливают/просматривают статус соединения (операции SetStatus/GetStatus ), прерывают соединение (операция ReleaseConnect ).
Interface Connect{
int EstablishConnect (int status);
int SetStatus (int status);
int GetStatus ();
int ReleaseConnect(connection*);}
В UML, к сожалению, возможны только односторонние интерфейсы. Это соответствует концепции интерфейса в RPC ( Remout Procedure Call ), Java и других программных технологиях. Однако в системах реального времени и, в частности, в телекоммуникационных системах, дело обстоит по-другому, и есть потребность в двусторонних интерфейсах. Например, в стандарте GSM подробно описывается, что на запрос на установку соединения со стороны мобильной телефонной трубки наземная сеть может прислать либо подтверждение, что запрос принят, либо отказ в связи с плохим финансовым положением абонента, либо отказ из-за сетевых сбоев. Формат и параметры каждого из этих четырех возможных ответов тщательно описываются в стандарте. Естественно поместить и сам запрос, и все возможные ответы в один интерфейс. Назовем его I. Тогда две компоненты, взаимодействующие через такой интерфейс, будут связаны: одна - с интерфейсом I, другая - с интерфейсом I*. Последний называется сопряженным интерфейсом. Например, если в интерфейсе I посылаются сообщения m1 и m2, а принимаются - m3 и m4, то в интерфейсе I* - все наоборот. Так сделано, например, в ROOM .
Односторонние интерфейсы UML сложно расширить до двухсторонних, так как, например, в графической нотации явно указано, какая компонента реализует, а какая использует интерфейс. В случае же двустороннего интерфейса акцент на реализации и использовании интерфейса не нужен. Этот случай - пример того, как общий язык моделирования не может быть удобно использован в конкретной предметной области.
Порт
Что такое интерфейс - понятно. Тем более, что интерфейс присутствует в общеиспользуемых языках программирования, таких как Java, а также в компонентных технологиях, например COM, Java Beans и пр.
Порт (port) - это точка, через которую происходит взаимодействие компоненты с окружающей ее средой. Именно с портом, а не с компонентой вообще связываются интерфейсы, которые компонента реализует и/или требует для своей работы. Порт аналогичен аппаратному разъему, например, USB-разъему компьютера.
Порт позволяет также легко реализовать концепцию однотипного соединения. Одинаковых разъемов у аппаратного модуля может быть много, например, три USB-разъема у компьютера. Чтобы компактно промоделировать эту ситуацию, можно сказать, что у компоненты "Компьютер" имеется порт с множественностью три, реализующий USB-интерфейс. А поскольку у композитных компонент часто возникают однотипные соединения, то множественный порт оказывается крайне полезной конструкцией. На рис.10.5в порт компоненты "МатеринскаяПлатаТРС" с интерфейсом DDR (для подключения микросхем оперативной памяти) имеет множественность 8, порты с интерфейсами PCI и IDE имеют множественность 2.
Порт принадлежит типу компоненты, а у роли компоненты могут быть, соответственно, экземпляры порта. У порта может быть имя, хотя на рис.10.5 такие имена отсутствуют, а есть только имена интерфейсов, соединенных с портами. Использовать или нет имена у портов - вопрос вкуса. Однако не стоит загромождать диаграмму какой-либо информацией без настоятельной необходимости.
Далее, говоря про экземпляры порта, я буду для краткости называть их просто портами, надеясь, что читатель не запутается, уяснив из контекста, про что в точности идет речь.
Соединитель
Концепция соединения очень важна в СРВ, в частности, в телекоммуникационных системах. Как было показано выше, многочисленные соединения между узлами телекоммуникационной аппаратуры "перекочевывают" в ПО телекоммуникационных систем. И эти соединения должны вести себя также, как и аппаратные соединения: их нужно уметь устанавливать, поддерживать, завершать (в том числе, и аварийно), восстанавливать после сбоя и т. д. Кроме того, все чаще именно компоненты ПО, а не аппаратура, участвуют в непосредственной передаче данных, реализуя протоколы верхних уровней стека сетевых протоколов. При этом они часто распределены по сети и пользуются готовыми программно-аппаратными реализациями нижних уровней для передачи данных. Так что возникает надобность "прокладывать" каналы напрямую от одной ПО-компоненты к другой.
Теперь более формально о том, как такие соединения реализуются в UML 2.0. Роли компонент, через экземпляры портов, соединяются друг с другом соединителями (connectors). Соединители могут связывать экземпляр порта у некоторой роли с портом типа компоненты, в который входит данная роль (пример см. на рис.10.5б ). Соединители могут иметь направление и множественность на концах.
Соединители должны связывать между собой только те экземпляры портов, которые совместимы. Совместимость пары портов определяется через согласованность связанной с ними пары интерфейсов. Потому что если, например, две телекоммуникационные компоненты взаимодействуют через сеть, то их интерфейсы должны быть частями одного протокола. В ответ на посылку сообщения m1 компонента ожидает получить сообщения m2 или m3 или m4. А если она вместо этого получает сообщение m5, которое вообще не предусмотрено к обработке в этой компоненте, то система в этот момент вряд ли работает правильно.
Однако понятие согласованности интерфейсов в UML не определяется формально.
Согласованность двусторонних интерфейсов определяется просто: если интерфейс I1 является сопряженным к I2, то, значит, они совместимы и соответствующие экземпляры портов можно связывать соединителем. В данном случае, когда у нас есть только односторонние интерфейсы, совместимость нужно как-то явно задавать, например, что I1 совместим с I2 и I3. При связывании двух экземпляров портов соединителем графический редактор должен проверить, являются ли их интерфейсы совместимыми. И если не являются, соединитель не должен быть создан. В UML никак не определяется концепция согласованности интерфейсов.
Множественность на концах соединителя аналогична множественности концов ассоциации. Ведь роли компонент, которые связывает соединитель, похожи на классы, которые связывает ассоциация, - и те и другие определяют наборы экземпляров. Соответственно, ассоциация переходит в связь между экземплярами, и соединитель - тоже. Однако в СРВ не приняты связи "один-ко-многим" и уж тем более "многие-ко-многим".
Исключение составляют широковещательные (broadcast) соединения, а также концепция сервисных соединений, реализованная, например, в ROOM . Множественность соединителей используется, в основном, для ролей в других структурных классификаторах, например, в кооперациях.
В примере на рис.10.5 концы всех соединителей имеют множественность 0..1, и на диаграммах рассматриваемого примера она не показана. Значение множественности 0 реализуется, когда экземпляр компоненты не имеет данного соединения. Когда он его устанавливает, то реализуется значение множественности, равное единице.
Соединитель называется делегирующим, если он связывает порт типа компоненты с портом роли и роль при этом находится внутри данного типа. Такой соединитель позволяет реализовывать транзитные соединения, проходящие через границу типа компоненты. Ведь снаружи компоненты ее части не видны и с ними нельзя связаться непосредственно. А с помощью таких транзитных соединений это осуществимо. Пример делегирующего соединителя можно увидеть на рис.10.5б - от роли ":Видеокарта" к порту компоненты "СистемныйБлокТРС", у которого есть VGA -интерфейс.
Как уже упоминалось выше, соединитель является аналогом провода, соединяющего два аппаратных устройства. В языке SDL, предназначенном для моделирования телекоммуникационных систем, соединителям UML соответствуют каналы, где можно определять сообщения и много других интересных с точки зрения телекоммуникаций свойств. В методологии ROOM, откуда соединители попали в UML, они имеют более строгую формальную семантику. Соединители в UML развились из ассоциаций с попыткой обобщить концепцию соединения ролей. С их помощью соединяются, например, роли классов на диаграммах композитных структур для коопераций. При этом там порты не используются (можно считать, что они есть, но являются фиктивными и на диаграмме не показываются).
Порты компонент, которые соединяются делегирующими соединителями с ролями внутри этих компонент, называются транзитными. Порт с интерфейсом VGA на рис.10.5б является транзитным. Порты, которые не соединены с частями компоненты, называются оконечными. Они ведут, например, к той части компоненты, которая не состоит из других компонент, то есть к собственному поведению компоненты. Собственное поведение часто определяют с помощью диаграмм конечных автоматов. На рис.10.5б оконечным портом является тот, который предназначен для включения в электрическую сеть (с интерфейсом 220В).
Выводы
Теперь становится понятно, откуда взялись абстракции, используемые при моделировании структуры систем реального времени.
- Аппаратные узлы и провода перешли в композитные компоненты и соединители.
- Разъемы и аппаратные интерфейсы, а также сервисные точки многоуровневых сетевых моделей перешли в порты и интерфейсы.
- Принцип блочной декомпозиции был "навеян" модульностью аппаратуры и укрепившимися в телекоммуникациях многоуровневыми моделями сетевых протоколов.
При проектировании сложных СРВ далеко не сразу разделяют программную и аппаратную части. Вначале, как правило, создается единая модель системы. Например, многоуровневые модели сетевых протоколов, которые при разработке телекоммуникационных систем часто используют в качестве исходных моделей, эволюционизирующих в модели архитектуры СРВ, не содержат указаний на то, какая их часть должна быть реализована программно, а какая - аппаратно. Декомпозицию программно-аппаратных систем удобно проводить с помощью модельных абстракций, которые подходят как для ПО, так и для аппаратуры.
Композитные компоненты UML незаменимы при разработке структурно сложных СРВ, в частности, сложных телекоммуникационных систем. Однако на практике встречается много структурно простых СРВ. Такие системы могут быть глубоко встроенными в аппаратуру и управлять одной-двумя "железками", имея всего несколько программных компонент и небогатый внешний интерфейс. При этом системы могут быть достаточно сложны - например, реализовывать сложные математические алгоритмы. Однако при разработке таких систем, наверное, "городить огород" с UML-компонентами, портами, соединителями и пр. не стоит…
Композитная компонента UML 2.0 является частным случаем структурного классификатора (structured classifier) - конструкции, предназначенной для блочной декомпозиции различных типов. В данном курсе рассматривались еще несколько UML-сущностей, которые, на самом, деле являются структурными классификаторами - это кооперация и класс. Таким образом, и компонента, и кооперация, и класс, а также их роли, могут показываться на диаграммах композитных структур. Роли внутри структурного классификатора соединяются соединителями, но вот порты используются только для компонент. Структурные классификаторы, соответствующие ролям, сами, в свою очередь, могут раскрываться через другие роли и так далее… Авторы UML привнесли в версию стандарта 2.0 блочную декомпозицию из ROOM, обобщив ее на другие виды структурных конструкций. На мой взгляд, это сильно ослабило выразительную силу механизма блочной декомпозиции для компонент, а также сильно запутало его для изучения: разобраться, что такое роль и структурный классификатор - непросто…
Реактивные системы
Выше были рассмотрены средства моделирования UML 2.0 структуры СРВ. Теперь перейдем к моделированию поведения СРВ.
Рассмотрим класс СРВ под названием реактивные системы (reactive systems). Такие системы обладают следующими свойствами.
- Организованы в виде параллельно работающих компонент.
- Постоянно взаимодействуют с окружением, причем это взаимодействие может носить асинхронный, непредсказуемый характер.
- Обладают прерываемостью, т. е. должны быть готовы обрабатывать запросы наивысшего приоритета.
- Их реакция на внешние запросы имеет строгие временные ограничения.
- Сценарии работы таких систем зависят от их предыдущего поведения (истории).
Считая, что первые четыре свойства в достаточной мере очевидны, рассмотрим лишь последнее свойство - зависимость поведения системы от истории. Вот пример. Пусть некоторый человек едет в переполненном автобусе. Другой человек ему наступает на ногу. Первый делает вид, что не замечает, досадливо морщась. Когда тот же человек наступает ему на ногу во второй раз, первый может заметить вслух, что пора бы прекратить это делать. Но когда это же происходит в третий раз, в автобусе происходит скандал. В моменты, непосредственно предшествующие "наступанию" на ногу - внешнему событию для компоненты "Потерпевший" - данная компонента находилась в трех разных состояниях, напрямую зависящих от предыдущих событий. И ее реакция на одно и то же событие в этих состояниях разная.
Итак, в компонентах реактивных систем целесообразно заводить различные состояния, фиксирующие определенный момент в истории их жизни. И обработка одних и тех же внешних событий компонентами в этих состояниях будет различной.
Среди СРВ встречается большое количество реактивных систем. Однако, не все СРВ таковы. Например, какой-нибудь шифратор сетевых сообщений просто обрабатывает входной поток внешних сообщений, шифруя их и выдавая дальше, в сеть. В нем может быть всего несколько состояний: старт, нормальная работа, завершение работы, переполнение. Нет никакой прерываемости, отсутствует зависимость от истории, взаимодействие с окружением синхронное.
Реактивные системы, равно как и диаграммы состояний и переходов (state transition diagrams), которые вошли в UML под названием диаграмм конечных автоматов, ввел Дэвид Харел в 80-х годах прошлого века. С результатами Харела можно ознакомиться в книге. Впоследствии, основываясь на этих идеях, компания x-Logic реализовала, пожалуй, самое мощное средство проектирования систем реального времени на основе конечных автоматов. В настоящий момент эта компания куплена шведской компанией Telelogic AB (http://www.telelogic.com) - одним из крупнейших производителей средств UML-моделирования.
10.9. Обзор примера
Далее рассмотрим пример - упрощенный фрагмент телекоммуникационной системы из области мобильной телефонии. Данная система реализует стык пользовательского интерфейса мобильного телефона (клавиатурный ввод и дисплейная индикация) и верхнего уровня интерфейса телефона с наземной сетью.
Эта система, без сомнения, является реактивной. Она асинхронно взаимодействует с пользователем телефона и сетью. Она прерываема - например, обязана обслуживать экстренные вызовы (связь с МЧС), находясь практически в любых состояниях. Работа системы ограничена строго заданными временными интервалами (это, к сожалению, в примере будет затронуто лишь слегка, иначе он получился бы слишком сложным). Система работает по различным сценариям, имея, кроме главного, "хорошего" сценария, множество "боковых веток". Наконец, архитектура системы организована в виде параллельно работающих компонент.
Этим примером хотелось продемонстрировать, что в случае реактивных систем целесообразно создавать UML-спецификации с последующей автоматической генерацией конечного кода приложения. Как уже отмечалось выше, генерация целевого кода по UML-диаграммам желательна, но часто наталкивается на препятствия. Таким препятствием является отсутствие емких визуальных абстракций - одновременно и наглядных, и имеющих полноценную исполняемую семантику, достаточную для эффективной генерации целевого программного кода.
Успешными средствами моделирования реактивных систем являются диаграммы композитных структур (вариант для компонент) в связке с диаграммами конечных автоматов. Конечный автомат создается для композитной компоненты. Он использует сообщения и операции, определенные в интерфейсах компонент, для взаимодействия с окружением. В паре два этих типа диаграмм позволяют создавать содержательные и наглядные спецификации, по которым генерируется целевой код СРВ. Все это и хочется продемонстрировать.
На рис.10.6 представлена диаграмма последовательностей, на которой обозначены главные действующие лица примера и основной сценарий их взаимодействия. Сразу после включения телефонной трубки (пользователь нажал нужную кнопку на аппарате) клавиатура (Keybroad), с помощью сообщения Switch-on, создает компоненту UserDriver, которая, в свою очередь, создает компоненту Main. После этого компонента UserDriver, с помощью сообщения DPINInput, посылает дисплею команду отобразить приглашение на ввод PIN. Введенный пользователем PIN пересылается с клавиатуры компоненте UserDriver, а та переправляет его компоненте Main, где происходит его проверка. Если PIN правильный, то компонента Main начинает искать сеть, посылая оповещение об этом компоненте UserDriver (сообщение SearchForPLMN). Последняя, в свою очередь, командует дисплею отобразить картинку поиска сети (сообщение DSearchPLMN ). Когда сеть найдена, Main посылает UserDriver сообщение HomePLMN, и та, после его получения, предлагает дисплею отобразить картинку-сообщение, что полный сервис телефона доступен пользователю (сообщение DHomePLMN). Компонента Main переходит в состояние Idle и готова обслуживать запросы пользователя телефона и входящие запросы из сети.
Рис. 10.6. Главный сценарий примера
Из четырех сущностей, представленных на диаграмме с рис.10.6, нас интересуют только две - UserDriver и Main. Оставшиеся не являются программным обеспечением, поэтому здесь не рассматриваются. Компоненты UserDriver и Main представлены на рис.10.7.
Рис. 10.7. Компоненты Main и UserDriver
В нашем примере поведение компоненты Main определяется с помощью диаграммы конечных автоматов. Иллюстративный вариант этой спецификации представлен на рис.10.8. Данная диаграмма создана для того, чтобы функциональность компоненты Main стала понятной в первом приближении: имена состояний и события обозначены по-русски, нет описания действий в переходах и т. д. Формальная спецификация поведения компоненты Main будет представлена ниже (нетерпеливые могут посмотреть уже сейчас на рис.10.17). UML позволяет создавать такие промежуточные, эскизные спецификации и хранить их наравне с основными.
Рис. 10.8. Иллюстративное описание поведения компоненты Main
Прокомментируем рис.10.8. При включении трубки компонента Main переходит в состояние "Ожидание PIN". Если PIN введен верно, то далее компонента Main оказывается в состоянии "Поиск сети" и ищет свою сеть - ту, в которой абонент зарегистрирован и информация о которой хранится в его трубке. Если своя сеть найдена, то происходит переход в состояние "Ожидание запроса". В этом состоянии компонента Main способна обрабатывать запросы на установку соединения - как исходящего, от абонента трубки, так и входящего, поступившего из сети. Но эта функциональность уже отсутствует в нашем примере, дабы не усложнять его чрезмерно.
Это был самый "хороший" сценарий, в результате исполнения которого телефонная трубка включилась и готова обслуживать абонента. Теперь рассмотрим самый "плохой" сценарий, когда телефон есть, сеть есть, а позвонить никуда, кроме как в милицию, нельзя (гм, что может быть хуже…). В состоянии "Ожидание PIN" компонента Main может принять три попытки ввода неверного PIN. Если вторая или третья попытка окажется удачной, то дальнейшие события разворачиваются так, как описано выше. Если же и в третьей попытке был введен неверный PIN, то происходит переход в состояние "Ожидание PUK ". В этом состоянии принимается десять попыток ввести верный PUK и после десятой попытки происходит переход в состояние "Блокирована". Если компонента Main находится в этом состоянии, то трубка может позволить абоненту сделать только экстренный выз ов (милиция, скорая помощь), а также выключить трубку. Теперь рассмотрим различные "боковые вет