Программа для облегчения написания конечных автоматов¶
- Table of contents
- Программа для облегчения написания конечных автоматов
ОБЩЕЕ ОПИСАНИЕ
semcpp.exe - программа для облегчения написания конечных автоматов на C, основанных
на библиотеках классов/шаблонов cclasses и aclasses.
semcpp читает текстовый файл (обычно с расширением .sem) и генерирует .cpp и .h файлы из него.
По сути, .sem файлы написаны на C, однако с некоторыми небольшими расширениями, позволяющими
сильно сократить объем C кода (текста), который необходим для того, чтобы воспользоваться
библиотекой шаблонов aclasses вручную.
в командной строке для semcpp задается список имен .sem файлов. Если расширение опущено, то
подразумевается .sem. Файлы компилируются один за другим, каждый должен содержать определение
отдельное и законченное определение автомата. Из каждого файла генерируется два файла (.cpp и .h)
с тем же именем, что и исходный .sem файл. По умолчанию файлы записываются в тот же каталог, где
лежит исходный .sem файл, однако semcpp имеет два ключа -h и -s с помощью которых можно указать
пути для записи файлов.
ВХОДНЫЕ ФАЙЛЫ
semcpp просматривает входной файл два раза. в первом проходе она строит таблицы всех объектов,
определенных в .sem файле, затем выполняется ряд проверок целостности и непротиворечивости
описания автомата, затем выполняется второй проход, во время которого semcpp записывает
сгенерированный C код в выходные файлы, и переносит в .cpp файл содержимое .sem файла, которое
к ней не относится (т.е. части кода автомата написанные на C).
semcpp не выполняет полный синтаксический разбор и анализ текста в .sem файле, и в этом смысле
является типичным препроцессором. Полный разбор выполняется только для частей файла, которые
помечены специальными директивами для semcpp. Синтаксически каждая такая директива начинается и
заканчивается символом "$". Как правило, секции объявлений или кода предназначенные только для semcpp,
начинаются с директивы указаного вида и заканчиваются директивой $End$. Каждая такая секция
объявлений полностью разбирается semcpp на первом проходе по исходному файлу, и информация
из нее заносится в таблицы semcpp. На втором проходе semcpp удаляет эти секции из текста исходного
файла, в текст выходного .cpp файла они переносятся только в виде комментариев.
Остальная часть текста исходного файла считается C кодом, и практически без изменений переносится
в выходной .cpp файл.
Обычные С комментарии в обоих стилях можно использовать в .sem файлах без ограничений. semcpp
опознает при разборе текста комментарии и строчные литералы, и они могут без ограничений
содержать символы "$" и строки, совпадающие с директивами semcpp. Комментарии без изменений
переносятся в выходной .cpp файл.
ГЕНЕРИРУЕМЫЙ КОД
По сути, С код, сгенерированный semcpp, состоит из определения и реализации двух С классов,
выведенных из соответствующих базовых шаблонов библиотеки aclasses (semtempl.h):
1) класс определения конечного автомата, выведенный из шаблона
template< typename MachineInstanceT, typename EventT >
class TStateEventMachineDefinition;
Этот класс содержит таблицу событий автомата, таблицу состояний, таблицу переходов, и таблицу
объявлений таймеров.
2) класс экземпляра конечного автомата, выведенный из шаблона
template< typename MachineInstanceT, typename EventT,
typename ThreadT = TStateEventMachineThread< EventT > >
class TStateEventMachineInstance;
Этот класс содержит текущее состояние автомата, экземпляры таймеров для данного экземпляра автомата,
некоторое количество других данных (например, ссылки на объект класса определения и объект потока,
исполняющего автомат, номер экземпляра автомата, и т.п.).
Объявления этих двух класссов semcpp заносит в .h файл. Помимо этого, semcpp заносит в заголовок
объявленные коды внешних событий, некоторые другие объявления.
Реализация методов сгенерированных классов заносится в .cpp файл. Туда же semcpp копирует весь
остальной код из входного файла.
Таблица переходов в классе определения автомата содержит указатели на члены-методы класса экземпляра.
Индексируются переходы парой <код состояния,код события>, коды состояния и события представляют собой
32-битовые беззнаковые целые числа.
ОБЪЕКТ ПОТОКА
Для работы экземпляров автоматов необходим один или несколько потоков, исполняющих код автоматов
и обрабатывающий события. semcpp не генерирует код,который связан с этой частью. Такой код должен быть
написан вручную с использованием одного из базовых шаблонных классов из aclasses
template< typename EventT >
class TStateEventMachineThread;
или
template< typename EventT >
class TStateEventMachineThreadPool;
Объект первого типа содержит очередь событий, предназначенных для обработки автоматами, а также таблицу
связанных с этой очередью и потоком экземпляров автоматов. Объект второго типа содержит несколько пар
поток/очередь вместо одной. Объекту экземпляра автомата аргументом конструктора при создании передается
ссылка на объект потока, и экземпляр автомата регистрирует себя в таблице автомата потока.
Практически, по минимуму можно вывести класс потока ничего не добавляя в производном классе, создать
объект потока, и из любого другого потока вызвать его метод RunMachine(). После этого объект потока
способен передавать события экземплярам автоматов. Создавать экземпляры автоматов можно в любой
момент после создания объекта потока.
Один экземпляр автомата всегда исполняется только одним потоком ОС. Это верно даже в случае использования
второго базового класса (thread pool). Один поток ОС может исполнять один или несколько автоматов.
СОБЫТИЯ
Связанные с событиями типы также должны быть описаны вручную в отдельном заголовке. semcpp не
генерирует этого кода, однако должна знать имя типа события для автомата для генерации своего кода.
События в модели автоматов, используемой semcpp, представляют собой произвольные структуры
любых двоичных данных, с тем ограничением, что они должны иметь заголовок фиксированной структуры,
состоящий из трех 32-битовых слов:
- первое слово: код события
- второе слово: номер экземпляра автомата
- третье слово: длина данных в байтах, следующих далее.
С терминах C такие конструкции оформляются как структуры без конструкторов/деструкторов и без
виртуальных функций, выведенные из структуры AgentEventHeader (cclasses/msgdefs.h), описывающей
заголовок. Для того, что следует после заголовка, часто используется union.
Помимо AgentEventHeader, в заголовке msgdefs.h библиотеки cclasses определены также типы
AgentTimerEventBody и AgentChannelEventBody. Эти две структуры описывают форматы событий, посылаемых
таймерами при их истечении и каналами, организованными на сокетах (примечание: имеются в виду события
изменения состояния каналов - открытие, закрытие, разрыв соединения, и т.п.). Поэтому типичное
объявления структуры для событий автомата выглядит, например, так:
struct TcpServerEventIn : public AgentEventHeader
{
union{
AgentTimerEventBody timer; // события таймеров
AgentChannelEventBody channel; // события каналов
TcpServerInputExtMessageBody agent; // внешние события
TcpServerInstanceAckBody ack; // внешние события
TcpServerEventConnectedClient peer; // пакеты данных из канала
}body;
// host/network byte order conversions
void h2n( );
void n2h( );
};
Примечание: методы n2h() и h2n() используются шаблонами каналов библиотеки aclasses, и их можно не
определять, в тех случаях, когда каналы не используются.
Источниками событий для экземпляра автомата в общем случае являются:
- другие автоматы (или вообще любой другой код): объект потока имеет набор методов EnqueueEvent(),
BroadcastEvent(), они потокобезопасны;
- он сам: экземпляр автомата имеет методы EnqueueEvent(), EnqueueEventSelf();
- таймеры, объявленные в соответствующем разделе .sem файла или созданные экземпляром при помощи
явного вызова CreateTimer(), и запущенные в экземпляре автомата вызовом StartTimer();
- каналы на сокетах: они посылают извещения об изменении состояния и ошибках передачи/приема,
а также suspend/resume при увеличении буфера посылки до максимального размера.
- прием данных в каналах.
Для маршрутизации событий объект потока использует "номер экземпляра автомата" из заголовка события.
Его может получить любой код приложения с помощью публичного метода класса экземпляра автомата
InstanceId(). Экземпляру автомата этот код присваивается объектом потока в момент создания
экземпляра, и вставки его в список экземпляров автоматов, исполняемых потоком.
ОБЪЯВЛЕНИЕ АВТОМАТА В .sem ФАЙЛЕ ($Machine$)
Секция объявления машины в .sem файле выглядит следующим образом:
$Machine$
Name = "machine name"; // used for trace/logging purposes only
EventStruct = event-struct-typename, "header-file-name"; // event struct for listening machine
InstanceClass = instance-class-typename; // machine instance class name to be used by semcpp
DefinitionClass = definition-class-typename, def-object-name, def-ref-membername; // machine definition class
ThreadClass = thread-class-typename, thread-ref-membername, "header-file-name"; // machine thread class
ChannelCollectionPointer = pointer-name, "header-file-name"; // channel collection pointer object
ConfigFile = config-file-section, config-ref, "header-file-name"; // config file object data
InitialState = initial-state-constant; // constant must be listed in directive "$States$"
AuxiliaryHeaderIncludes = "header-file-name", "header-file-name", … ; // added to generated .h file
AuxiliarySourceIncludes = "header-file-name", "header-file-name", … ; // added to generated .cpp file
$End$
примечания:
- semcpp генерирует в .cpp файле объявление объекта определения автомата. Если параметр def-object-name
в директиве DefinitionClass не пуст, то для объекта определения автомата используется это имя, и его
extern-объявление помещается в .h файл. Также, конструктор класса экземпляра в этом случае будет
иметь первым параметром ссылку на объект определения. Если параметр пуст, то semcpp присвоит имя
объекту определения автомата сама.
- semcpp генерирует в классе экземпляра автомата член-ссылку на объект определения автомата. Если
параметр def-ref-membername в директиве DefinitionClass не пуст, то это имя будет использовано как
имя этого члена класса экземпляра, иначе этот член класса экземпляра будет иметь имя "_def".
- имя pointer-name в директиве ChannelCollectionPointer должно публично приводиться к типу
ChannelCollection* (см.semchan2.h в библиотеке aclasses).
- директива ConfigFile задает имя секции для автомата в файле конфигурации, имя ссылки на объект
класса публично выведенного из класса Config, или класса реализующего тот же интерфейс (см.configx.h
в библиотеке cclasses), и заголовок, где она объявлена. Значения по умолчанию для полседних двух
параметров - "config" и "configx.h".
ОБЪЯВЛЕНИЕ СПИСКА СОСТОЯНИЙ АВТОМАТА В .sem ФАЙЛЕ ($States$)
Полный список состояний машины должен быть объявлен в секции "$States$":
$States$
stateName : "description" ;
. . . . . . .
$End$
semcpp генерирует константу для каждого состояния, и присваивает ей значение. Идентичные private
объявления включаются в объявления обоих классов - определения автомата и экземпляра автомата.
Класс определения автомата имеет метод StateName(), возвращающий строку "description" из директивы
semcpp.
Класс экземпляра автомата имеет методы State() и SetState() для получения текущего и установки
нового состояния автомата.
При генерации C файлов semcpp вставляет вызовы метода RegisterState() в конструктор класса
определения автомата.
ОБЪЯВЛЕНИЕ СОБЫТИЙ АВТОМАТА В .sem ФАЙЛЕ ($!ExternalEvents$, $!InternalEvents$ и $!ImportedEvents$)
Для объявления событий в .sem файле может использоваться четыре несколько различных по смыслу секции
списков кодов событий: $!InternalEvents$, $!ImportedEvents$, $!ExternalEvents$, $!ExportedEvents$.
Помимо этого, коды событий также неявно объявляются объявлениями таймеров и каналов.
semcpp различает три категории кодов событий:
- внутренние: объявленные явно как внутренние события в списке $!InternalEvents$ и в директивах описания
таймеров. Для таких событий константам значения присваиваются в .cpp файле, сгенерированном semcpp;
- внешние: объявленные явно как внешние события в списках $!ExternalEvents$ и/или $!ExportedEvents$
и в директивах описания каналов. Для таких событий константам значения присваиваются в .h файле,
сгенерированном semcpp;
- импортированные: для таких событий константам значения присваиваются где-то в других заголовках,
например, другого автомата, или написанных вручную. semcpp просто использует имена констант в коде
на C, но нигде не присваивает им значений. Подразумевается, что в этом случае такие заголовки
включены в списки "auxiliary header" директивы $Machine$.
Формат директив следующий:
$!ExternalEvents$
unsigned-int : event-const-name [, "description" ];
. . . . . . .
$End$
$!ExportedEvents$
unsigned-int : event-const-name [, "description" ];
. . . . . . .
$End$
$!InternalEvents$
[ unsigned-int ] : event-const-name [, "description" ];
. . . . . . .
$End$
$!ImportedEvents$
: event-const-name [, "description" ];
. . . . . . .
$End$
Каждая из директив может использоваться в .sem файле несколько раз, списки событий дополняются каждой
последующей директивой.
Для $!InternalEvents$ значения констант, если они не заданы, присваиваются semcpp. Константы, где они заданы,
могут быть записаны в десятичном и шестнадцатиричном видах.
Для известных кодов событий (т.е. кроме $!ImportedEvents$) semcpp проверяет, что коды событий
разных констант не совпадают.
При генерации C файлов semcpp вставляет вызовы метода RegisterEvent() в конструктор класса определения
автомата.
ТАЙМЕРЫ ($Timers$)
semcpp позволяет объявлять таймеры, которыми может пользоваться каждый созданный экземпляр автомата.
таймеры, объявленные в .sem файле автоматически создаются при создании объекта экземпляра автомата.
В объявлении каждого таймера указывается имя константы, которая идентифицирует код события таймера.
Константе можно явно присвоить значение, или оставить поле значения пустым, тогда semcpp присвоит
некоторый код события.
Запускать и останавливать таймеры можно вызовом методов экземпляра StartTimer() и CancelTimer().
Если таймер истечет, то он вызовет получение события с кодом, ему приписанным. Если таймер явно
отменен вызовом CancelTimer(), то событие не генерируется, стоящее в очереди уничтожается. Все методы
работы с таймерами для иденитификации таймера используют его индекс в таблице таймеров экземпляра
автомата (также по историческим причинам называемый в коде библиотеки timer job id или tjid). Для
объявленных таймеров объект определения автомата содержит дополнительные члены данных с именами,
совпадающими с именами таймеров, но с приписанным префиксом "tjid", эти члены содержат индексы
созданных для экземпляра таймеров.
Формат директивы semcpp для .sem файла (показаны четыре разных варианта строк списка):
$Timers$
[ unsigned-int-event ] : event-const-name , [ "description" ], unsigned-int-msec ;
[ unsigned-int-event ] : event-const-name , [ "description" ], unsigned-int-sec , second ;
[ unsigned-int-event ] : event-const-name , [ "description" ], unsigned-int-msec , millisecond ;
[ unsigned-int-event ] : event-const-name , [ "description" ], unsigned-int-sec , unsigned-int-nanosec ;
. . . . . . .
$End$
Первые три поля описывают событие, связанное с таймером, в точности аналогично директиве $!InternalEvents$.
Четвертое и пятое поля задают интервал, связанный с таймером:
- одно число: в миллисекундах;
- одно число и слово "second": в секундах;
- одно число и слово "millisecond": в миллисекундах;
- два числа: в секундах и наносекундах.
Пример: Если в качестве имени события указано MyTimeout, то запускать и останавливать такой таймер
можно вызовами:
- StartTimer( _def._tjid_MyTimeout );
- CancelTimer( _def._tjid_MyTimeout );
Помимо описанного выше способа, в переходах автомата и других методах экземпляра автомата таймеры можно
создавать динамически вручную явным вызовом метода tjid=!CreateTimer(…) объекта экземпляра.
Любые таймеры можно уничтожать вызовом DestroyTimer(tjid).
При уничтожении объекта экземпляра автомата, все запущенные им таймеры останавливаются, и все
созданные автоматически и вручную таймеры уничтожаются (что, впрочем, не отменяет правила
хорошего тона С: лучше явно уничтожить созданные вручную таймеры вызовом DestroyTimer()).
При генерации C файлов semcpp вставляет вызовы метода DeclareTimer() в конструктор класса
определения автомата, и присваивает значения соответствующих индексов tjid полям добавленным
в класс определения автомата. Объявления этих полей вставляются в объявление класса определения
автомата в .h файле.
Разрешающая способность таймеров соответствует разрешающей способност и таймаутов в системных вызовах:
- pthread_cond_timedwait() — Linux;
- SignalObjectAndWait() — Win32.
Примечание: Таймеры и генераторы частоты реального времени (cclasses/freqncy.h) semcpp в явном виде
не поддерживаются, но могут быть использованы в C коде.
КАНАЛЫ ($Channels$)
semcpp позволяет объявлять каналы обмена данными с другими машинами и/или приложениями, основанные
на сокетах. Функциональность соответствует реализации в semchan2.h (aclasses), и позволяет использовать
практически все виды обмена, доступные на сокетах.
Для датаграммных протоколов событием является одна пришедшая датаграмма. Заголовок событие (структура
AgentEventHeader) может содержаться в данных из сокета, а может быть добавлен парсером данных канала.
Для потоковых протоколов разбиение на события (и вставка заголовков если требуется) выполняется
парсером данных канала. Обмен на сокетах выполняется отдельными потоками ОС (реализованными внутри
C классов "коллекция каналов" из aclasses) и изменения состояния каналов (открытие, закрытие,
входящие соединения, ошибки, и т.п.) также являются событиями для экземпляра автомата, который
использует данный канал. Для использования каналов практически нужно только дописать класс парсера,
который обычно сильно зависит от структуры передаваемых данных и прикладных слоев используемого протокола.
semcpp умеет генерировать код для вставки каналов в коллекцию и их открытия/закрытия при запуске
и остановке экземпляра автомата. Объявление каналов для автомата в .sem файле выглядит следующим образом:
$Channels$
unsigned-int-event : event-const-name , [ "description" ], channel parameters . . . . . . ;
. . . . . . .
$End$
Описание параметров каждого объявления канала внутри директивы $Channels$:
// ————————————————————————————————————————————-
// syntax for channel declaration:
// ————————————————————————————————————————————-
// event : event_name, "description", || !0:2 || mandatory || channel status event
// classname, || 3 || mandatory || classname for this channel
// includename, || 4 || optional || header where the class is defined
// pointername, || 5 || mandatory || base name to derive some member names in machine instance
// constructor_args, || 6 || optional || additional constructor arguments
// {auto|manual}, || 7 || mandatory || type of setup
// for manual all remaining can be omitted; if provided then setup routine is generated
// sockettype, || 8 || mandatory || tcpserver/tcpclient/udplistener/udpsender/udpclient/raw/ipraw/name
// host, port, || !9:10 || mandatory || can be empty if socket does not need them
// peer-host, peer-port, || !11:12 || mandatory || can be empty if socket does not need them
// reconnect-time, || 13 || optional || in milliseconds
// maxconn, || 14 || optional || for tcpserver only
// protocol, || 15 || optional || for [ip]raw
// sndmaxnbufs, || 16 || optional || max size of send queue (note: tcpserver)
// sndlowatmrk, || 17 || optional || suspend threshold for send queue
// sndhiwatmrk, || 18 || optional || resume threshold for send queue
// rcvbufsize, || 19 || optional || in bytes (except tcpserver)
// rcvbufremg, || 20 || optional || in bytes (only stream sockets)
// rcvbufincr, || 21 || optional || in bytes (only stream sockets)
// rcvbufmaxsz || 22 || optional || in bytes (only stream sockets)
// ————————————————————————————————————————————-
Параметры в списке директивы описания одного канала после параметра ‘sockettype’ примерно соответствуют
полям структуры AgentChannelSetupStruct (aclasses/semchan2.h) и параметрам метода implAgentChannel::Setup().
Краткое описание смысла параметров:
- sockettype типсокета/протокол: tcpserver/tcpclient/udplistener/udpsender/udpclient/raw/ipraw
- host, port собственные хост/порт для bind(); обязательны для udplistener и tcpserver
если заданы для остальных типов, то тоже используются в bind()
- peer-host, peer-port удаленные хост/порт; обязательны для udpclient и tcpclient; используются connect()
если заданы для udplistener или udpsender то используются в i/o вызовах (sendto(),recvfrom())
если заданы для raw/ipraw - используются в connect()
если заданы для tcpserver то используются в accept()
- reconnect-time для setup_type=auto задает таймаут в миллисекундах для попытки переоткрытия канала после
разрыва соединения или неудачной попытки соединения
- maxconn только tcpserver, параметр listen(). Значение 0 - специальное, в этом случае tcpserver
при входящем соединении закрывает слушающий сокет, и открывает его вновь после разрыва
(нужно для TCP каналов типа peer-peer которые никогда не используют более одного входящего)
- protocol только [ip]raw: тип протокола для сокета, например IPPROTO_ICMP (default == 0)
- sndmaxnbufs максимальное количество ждущих операций Send() в выходной очереди данного канала
- sndlowatmrk ‘suspend threshold’ выходной очереди данного канала
- sndhiwatmrk ‘resume threshold’ выходной очереди данного канала
- rcvbufsize размер входного буфера канала в байтах
- rcvbufremg порог остатка входного буфера для его увеличения в байтах
- rcvbufincr размер на сколько увеличивать входной буфер при достижении порога в байтах
- rcvbufmaxsz максимальный размер входного буфера канала в байтах
Примечание: параметры размера выходной очереди и входного буфера канала не используются каналом
типа tcpserver, однако метод Accept() копирует их в параметры каналов соединений, принятых сокетом.
Прочие детали описания параметров для открытия сокета здесь опустим, лучше посмотреть комментарии
в заголовках aclasses/semchan2.h и cclasses/socketcl.h.
По сути, канал представляет собой открытый сокет, который может получать и принимать пакеты данных,
а также специальными событиями сообщать об изменениях своего состояния автомату. semcpp автоматически
создает члены класса экземпляра автомата, содержащие указатели на созданные (также автоматически, в
конструкторе экземпляра) объекты каналов. Именуются эти члены класса экземпляра как заданное значение
параметра ‘pointername’ с приписанным спереди символом подчеркивания. Из этого же имени создаются
некоторые внутренние имена членов, с которыми работает код, сгенерированный semcpp для вставки
каналов в коллекцию, а также параметры канала для config файла.
Если указан параметр ‘sockettype’, то semcpp сгенерирует специальный метод для открытия канала.
Метод получает имя сответствующее параметру ‘pointername’ с приписанным впереди префиксом "setup_".
Если параметр 7 в списке описания канала в $Channels$ имеет значение ‘auto’, то тогда semcpp
сгенерирует код для открытия канала после запуска автомата. В случае, если задано ‘manual’,
метод должен быть вызван автоматом из его кода. Если не указан параметр ‘sockettype’ и
последующие параметры списка (нужные для открытия канала), то метод не генерируется semcpp.
Классы каналов выводятся из базового шаблона AgentChannel< MachineThread, EventStruct >.
В качестве первого аргумента шаблона используется класс объекта потока, в качестве второго -
структура, описывающая события, передаваемые по каналу. Она чаще всего является частью структуры или
объединения для "полного" события автомата, использованного в директиве $Machine$. В частности,
она может не содержать стандартного трехсловного заголовка событий, в том случае, если по каналу
не передается этих данных, а они вставляются парсером канала.
Параметры classname, includename и constructor_args задают имя класса используемое для канала,
заголовочный файл, в котором этот класс описан, и дополнительные аргументы конструктора канала.
semcpp предполагает, что конструктор канала имеет три аргумента:
( ссылка_на_объект_потока, код_события_смены_состояний_канала, номер_экземпляра_автомата )
Если конструктору канала требуются дополнительные аргументы, то они могут быть добавлены
параметром constructor_args.
Типичные объявления классов каналов обычно весьма несложны, и не требуют ничего кроме определения
конструктора, Пример - определение класса для канала серверного слушающего TCP сокета:
==
C определение класса канала:
class TcpServerListeningChannel : public AgentChannel< TcpListeningMachineThread >
{
public: // constructor
TcpServerListeningChannel( TcpListeningMachineThread& thrq, AgentEventId_t eId, AgentInstanceId_t iId );
: AgentChannel< TcpListeningMachineThread >( "listening_channel", thrq, _dummy_parser, eId, iId ),
_dummy_parser( )
{}
private: // internal data
DummyChannelParser _dummy_parser;
};
определение канала в .sem файле:
$Channels$
3 : eventListeningSocket, "listening socket event",
TcpServerListeningChannel, "tcplchan.h", tcp_server_channel, /* aux_constructor_args */,
auto, tcpserver, localhost!, 34567!, /* peer host /, / peer port */,
/* reconnect time /, / maxconn / 5!, / protocol /, / max send buffers */ 6!,
/* send low water mark / 4!, / send high water mark */ 1!,
/* receive buf size / , / receive buf remaining threshold */,
/* receive buf increment /, / receive buf max size */ ;
$End$
==
Еще пример - определение структуры сообщения и канала для входящего TCP соединения:
==
C определение класса канала:
struct TcpServerEventConnectedClient
{
char outstring[ 1 ];
// host/network byte order conversions
void h2n( ){}
void n2h( ){}
};
class TcpServerConnectionChannel : public AgentChannel< TcpListeningMachineThread, TcpServerEventConnectedClient >
{
public: // constructor
TcpServerConnectionChannel( TcpListeningMachineThread& agnt, AgentEventId_t eId, AgentInstanceId_t iId );
: AgentChannel< TcpListeningMachineThread, TcpServerEventConnectedClient >( "connection_channel", thr, _tcp_parser, eId, iId ),
_instanceId( iId ),
_tcp_parser( iId )
{}
private: // internal data
AgentInstanceId_t _instanceId;
TcpServerConnectionChannelParser _tcp_parser;
};
(здесь использован еще класс TcpServerConnectionChannelParser, о парсерах каналов - см в отдельном разделе).
определение канала в .sem файле:
$Channels$
12 : eventConnectionSocket, "connection socket event",
TcpServerConnectionChannel, "tcpcchan.h", tcp_connection_channel, /* aux_constructor_args */,
manual, /* tcpserver /, / host /, / port /, / peer host /, / peer port */,
/* reconnect time /, / maxconn / , / protocol /, / max send buffers */,
/* send low water mark /, / send high water mark */,
/* receive buf size / , / receive buf remaining threshold */,
/* receive buf increment /, / receive buf max size */ ;
$End$
==
Методы объектов каналов примерно соответствуют логике обычной работы с сокетами (BSD,
без расширений Win32), однако получение данных из сокета канал выполняет сам.
Автомат получает только данные в виде обычной доставки событий. В случае сокетов c протоколами,
ориентированными на датаграммы (как UDP или ICMP), каждая входная датаграмма из сокета является
событием. В случае сокетов с байтовыми потоками (как TCP), канал разрезает поток на входные события
с помощью встроенного объекта специального класса - парсера потока (см. отдельный раздел ниже).
Посылка данных выполняется вызовом метода AgentChannel<>::Send() из кода автомата. Есть несколько
вариантов этого с типом/без типа, с явно заданной длиной, с адресом хоста куда посылать (для
connectionless протоколов вроде UDP), и т.п.
Внутренняя реализация метода Send() не посылает данных, а ставит их в выходную очередь канала.
Данные в действительности могут быть посланы позже, когда в выходном буфере сокета появится место.
Чтобы избегать переполнения выходной очереди, каналы поддерживают механизм suspend/resume,
который управляется параметрами sndmaxnbufs, sndlowatmrk, sndhiwatmrk в директиве $Channels$.
Помимо метода Send(), следующие методы базового класса AgentChannel<> могут использоваться
внутри автомата для работы с каналами:
Accept( ссылка на другой объект канала ) // принять входящее соединение (только слушающий сокет)
Refuse( ) // отвергнуть входящее соединение (только слушающий сокет)
Setup( … ) // открыть сокет
Release( … ) // закрыть сокет
Примечание: для вызова Setup() semcpp строит метод с предопределенным именем в случае если тип
сокета задан в $Channels$. Если нет, то строить параметры Setup() и вызывать ее придется своим кодом.
ПЕРЕХОДЫ АВТОМАТА ($Transition$)
Переходы автомата из одного состояния в другое программируются как методы класса экземпляра автомата.
semcpp поддерживает специальный синтаксис для объявления и записи кода переходов, и при генерации
выходных файлов на С автоматически вставляет прототипы методов в определение класса экземпляра,
а код методов - в .cpp файл. Все методы переходов автомата имеют единственный аргумент - ссылку на
тип, указанный в директиве EventStruct секции $Machine$.
Формат директив semcpp для описания переходов автомата следующий:
$Transition$( states : events; evar )
. . . C code for transition . . .
$End$
‘evar’ - имя аргумента для функции, которая будет объявлена в классе экземпляра. Оно подчиняется
обычным правилам именования переменных C и может быть использовано в С коде перехода. Аргумент
функции имеет тип ссылки на структуру события. В том случае, если С код перехода не использует
информацию из структуры события, имя в директиве $Transition$ можно опустить вместе с точкой с запятой.
‘states’ - список имен состояний автомата, объявленных директивой $States$. Вместо него можно
использовать символ ‘*’, тогда этот переход будет вызван для указанных вторым параметром событий
в любом состоянии. Если одно или несколько имен состояний указано явно, то данный метод будет вызван
только для указанных состояний и для указанных вторым параметром событий.
‘event’ - список имен событий, объявленных директивами описания событий, таймеров, или каналов.
Вместо списка имен событий тоже можно использовать символ ‘*’, или символ ‘?’ тогда этот переход
будет использован в случае поступления любого объявленного или даже необъявленного события
в состояниях, указанных первым аргументом. Можно указать список событий состоящий из обоих этих символов.
Нельзя одновременно указывать ‘’ для состояния и ’’ или ‘?’ для события.
semcpp проверяет, что используются легальные события и состояния, а также проверяет объявленные
переходы на дубли. Выполняются также некоторые другие проверки.
Если для сочетания "состояние-событие" определено более одного обработчика (что может случиться
когда все подходящие кроме одного из них имеют звездочки в аргументах), то при исполнении кода автомата
используется определенный порядок выбора обработчика. Именно: первым всегда ищется обработчик
для точного сочетания "состояние-событие" с текущим состоянием и наступившим событием. Если он не
найден в таблицах класса определения автомата, тогда ищется обработчик всех событий для данного состояния.
Если и он не найден, тогда ищется обработчик данного события для всех состояний. Наконец, если
не найден ни один обработчик, то вызывается метод DefaultTransition() в классе экземпляра.
Поскольку он виртуальный, его можно перегрузить выведением производного класса. Реализация по умолчанию
ничего не делает.
Во всех случаях вызывается первый найденный обработчик, после этого событие считается обработанным.
При генерации C файлов semcpp вставляет вызовы методов RegisterTransition(), RegisterEventHandler(),
RegisterStateUnexpectedEventHandler(), RegisterStateUnknownEventHandler(), RegisterStateHandlers()
в конструктор класса определения автомата. Если обработчик объявлен для нескольких сочетаний
"состояние-событие", то функция определяется один раз в .cpp файле, но регистрируется в таблице переходов
класса определения машины несколько раз.
Примеры:
$Transition$( S1 : E1; x ) // будет вызвана в состоянии S1 для события E1
. . . C code for transition . . .
$End$
$Transition$( S1, S2 : E1, E2; x ) // будет вызвана в случаях S1-E1, S1-E2, S2-E1, S2-E2
. . . C code for transition . . .
$End$
$Transition$( * : E1, E2; x ) // будет вызвана в любом состоянии для событий E1 и E2
. . . C code for transition . . .
$End$
$Transition$( S1, S2 : ?, *; x ) // будет вызвана в состояниях S1 и S2 для любых событий
. . . C code for transition . . .
$End$
ДОБАВЛЕНИЕ МЕТОДОВ В КЛАСС ЭКЗЕМПЛЯРА АВТОМАТА ($Func$ и $!PubFunc$)
Поскольку заголовочный .h файл c определением классов описания автомата и экземпляра автомата
генерируется semcpp, то нет возможности прямо редактировать определения этих классов. Конструкции
$Func$ и $!PubFunc$ предназначены для добавления методов в класс экземпляра автомата. Метод пишется
только один раз в .sem файле, semcpp извлекает из него прототип и включает его в описание класса
в .h файле.
Перед заголовком метода в .sem файле должна стоять конструкция $Func$ или $!PubFunc$, после закрывающей
круглой скобки прототипа и перед открывающей фигурной тела функции - конструкция $$.
Методы, объявленные с $!PubFunc$ становятся открытыми членами класса, $Func$ - закрытыми.
Пример:
$Func$ void MyInstanceClass::!DoSomething( char *x, unsigned z ) $$
{
// do something somehow somewhere and sometimes
}
ДОБАВЛЕНИЕ ПЕРЕМЕННЫХ - ЧЛЕНОВ КЛАССА ЭКЗЕМПЛЯРА АВТОМАТА ($Data$)
Для добавления членов класса экземпляра - переменных используется конструкция $Data$. Пример:
$Data$
unsigned uvar1;
const char *pch2;
$End$
КОНСТРУКТОРЫ КЛАССОВ СГЕНЕРИРОВАННЫХ semcpp И ИНИЦИАЛИЗАЦИЯ АВТОМАТА ($Args$, $Setup$, $!SetupComplete$)
Класс определения автомата имеет конструктор без аргументов. В .cpp файле semcpp всегда объявляет
единственный объект этого типа - класса памяти static. Имя и его видимость определяется параметрами
директивы $Machine$, раздел DefinitionClass.
Объекты класса экземпляра автомата всегда создаются приложением. Можно использовать любой тип памяти,
однако логика приложения должна обеспечивать время жизни данного объекта пока он обрабатывает события.
Класс экземпляра автомата имеет аргументами конструктора следующие переменные:
- ссылку на объект определения автомата - только в том случае, если задано его имя в $Machine$:!DefinitionClass.
- ссылку на объект потока - всегда.
- дополнительные аргументы, определенные директивой $Args$ semcpp.
Для дополнительных аргументов конструктора экземпляра semcpp создает также закрытые поля данных в классе
экземпляра; эти поля инициализируются аргументами конструктора. Имена полей заданы разделами директивы
$Args$.
Пример:
$Args$
_aux1 : int;
_aux2 : "const char *" ;
_aux3 : "const char " ;
_aux4 : "const char *" ;
$End$
Помимо этого, semcpp поддерживает директивы $Setup$ и $!SetupComplete$. Обе эти директивы позволяют
определить дополнительные функции инициализации объекта экземпляра.
$Setup$
С код
$End$
$!SetupComplete$
С код
$End$
Функция $Setup$ вызывается в начале тела конструктора, до создания объектов каналов. После успешного
создания объектов каналов эти объекты вставляются в коллекцию каналов. Процесс этот асинхронный, и
экземпляр автомата ожидает его завершения в специальном автоматически добавленном semcpp предстартовом
состоянии. После успешной вставки каналов в коллекцию, экземпляр автомата устанавливает начальное состояние
(заданное в секции $Machine$) и запускает процедуры открытия каналов, если это требуется в соответствии
с описанием канала в секции $Channels$. В этой точке вызывается $!SetupComplete$.
Более детально, различаются два случая при генерации кода C.
1) .sem файл не содержит объявлений каналов (нет секций директив $Channels$).
В этом случае сгенерированный код конструктора экземпляра выполняет следующие действия:
- вызов конструктора базового класса (внутри него в числе прочего экземпляр автомата вставляется
в список автоматов объекта потока);
- вызов функции $Setup$;
- установка начального состояния автомата в соответствии с директивой $Machine$/!InitialState;
- вызов функции $!SetupComplete$.
После выполнения конструктора автомат находится в начальном состоянии и ожидает событий.
2) .sem файл содержит объявления каналов (хотя бы один объявлен директивой $Channels$).
В этом случае сгенерированный код конструктора экземпляра выполняет следующие действия:
- вызов конструктора базового класса;
- вызов функции $Setup$;
- создание объектов каналов (внимание: возможны исключения bad_alloc, их надо обрабатывать);
- добавление объектов каналов в коллекцию (вызов метода коллекции AddChannel(объект));
- установка специального дополнительного (добавлено semcpp) состояния "wait for add channels".
В этой точке конструктор заканчивается и автомат ожидает событий от коллекции каналов,
извещающих, что канал добавлен.
Специальные добавленные semcpp функций переходов обрабатывают события от коллекции каналов
в дополнительном предстартовом состоянии. Когда все каналы вставлены в коллекцию каналов,
код, дополнительно сгенерированный semcpp, делает следующее:
- устанавливает начальное состояние автомата в соответствии с директивой $Machine$/!InitialState;
- для каждого канала вызывает метод Setup() с параметрами, объявленными директивой $Channels$;
- вызов функции $!SetupComplete$.
Теперь автомат находится в начальном состоянии, и ожидает событий, которые могут включать
события от каналов (соединение, разъединение и т.п.).
Примечание: код из директив $Setup$ и $!SetupComplete$ помещается semcpp в отдельные закрытые методы
класса экземпляра: implementInstanceSetup() и implementInstanceSetupComplete(). Эти методы и их
вызовы в подходящих местах генерируются только если секции $Setup$ и/или $!SetupComplete$ присутствуют
в исходном .sem файле.
Примечание: объекты каналов, если они созданы, уничтожаются деструктором объекта экземпляра автомата.
semcpp генерирует соответствующий код автоматически.
ЗАВЕРШЕНИЕ РАБОТЫ АВТОМАТА И УНИЧТОЖЕНИЕ ОБЪЕКТОВ ЭКЗЕМПЛЯРОВ (!ExitMachine(), $Cleanup$, $!StopCondition$)
semcpp в классе экземпляра генерирует специальный метод ExitMachine(), предназначенный для завершения
работы автомата. Этот метод должен быть вызван внутри кода любого перехода (или вызванной из него функции)
в тот момент, когда нужно остановить работу автомата.
semcpp различает четыре различных варианта завершения машины, и, в зависимости от варианта генерирует
несколько различный C код. Все варианты различаются двумя условиями:
- в .sem файле присутствует/отстуствует секция $Cleanup$;
- в .sem файле объявлен хотя бы один канал.
Если в .sem файле объявлен хотя бы один канал, то semcpp добавляет два внутренних состояния в автомат,
и в момент вызова ExitMachine() сгенерированный код вызывает метод закрытия Release(relTypeClose) для
каждого из объявленных каналов, затем переводит автомат в первое из этих дополнительных состояний.
В этом состоянии автомат ожидает прихода подтверждений от всех каналов о закрытии.
Когда события закрытия пришли от всех каналов, сгенерированный код переводит автомат во второе
дополнительное состояние и вызывает метод коллекции каналов RemoveChannel() для каждого из
объявленных каналов. При приходе подтверждения об удалении канала из коллекции, код автомата
уничтожает объект канала (delete) и обнуляет указатель на него. Когда это проделано для всех каналов
вызывается процедура завершения работы автомата.
Если в .sem файле не объявлено ни одного канала, то дополнительные внутренние состояния ожидания
не добавляются, и прямо из функции ExitMachine() сгенерированный код вызывает процедуру завершения
работы автомата.
Процедурой завершения работы автомата является код находящийся в секции
$Cleanup$
C код
$End$
Из кода этой секции semcpp строит метод implementInstanceCleanup() в классе экземпляра автомата,
он и вызывается в качестве процедуры завершения. Типичная обработка - послать сообщение другому
автомату, который создал данный экземпляр; предполагается, что тот автомат сделает delete на объект
данного автомата и уничтожит его.
Если секция $Cleanup$ отсутствует в .sem файле, то semcpp вместо вызова implementInstanceCleanup()
генерирует вызов метода StopMachine() в объекте потока. Вызов этого метода останавливает цикл обработки
событий в очереди объекта потока, и поток выходит, завершая свое выполнение. Этот способ применим
и удобен для "главного" автомата приложения, или части приложения, использующей один объект потока.
Есть еще одна секция обрабатываемая semcpp, которая может использоваться в автомате с каналами:
$!StopCondition$
C код возвращающий bool
$End$
Написанный в этой секции код C используется для проверки дополнительного условия останова автомата,
и должен возвращать значение true когда автомат можно остановить. semcpp превращает этот код в
метод класса экземпляра bool implementInstanceStopCond(). Этот метод вызывается в той же точке
автомата, где в первом дополнительном состоянии проверяется условие закрытия всех каналов.
Когда есть эта секция, автомат перейдет к удалению каналов и завершению только в случае
когда все каналы закрыты и выполнено дополнительно условие проверяемое секцией $!StopCondition$.
При использовании этой секции, код автомата, изменяющий проверяемое ей условие, в конце должен
вызывать внутренний метод класса экземпляра implementCheckAndBeginCleanup().
КОЛЛЕКЦИИ КАНАЛОВ
Если автомат использует каналы, то для работы с ними требуется внешний объект - коллекция каналов.
semcpp генерирует код для вставки каналов в коллекцию и удаления их из коллекции, однако сам
объект коллекции должен быть создан в приложении вне кода, сгенерированного semcpp.
В простейшем случае можно использовать следующие объявления:
=
файл ccptr.cpp:
ChannelCollectionPointer chcopointer;
файл ccptr.h:
extern ChannelCollectionPointer chcopointer;
файл опеделения автомата (.sem файл):
$Machine$
. . . .
ChannelCollectionPointer = chcopointer, "ccptr.h"; // channel collection data
. . . .
$End$
=
semcpp сгенерирует директиву препроцессора #include "ccptr.h" и в коде будет использовать
объект указателя chcopointer.
ПАРСЕРЫ КАНАЛОВ
Парсер канала - специальный объект, ссылка на который передается конструктору базового шаблона
AgentChannel<> для канала. Парсер используется для двух целей:
1) сканирования входящего потока байтов по каналу и разрезание потока на отдельные события
(для каналов использующих байтоориентированные потоковые протоколы как TCP);
2) для вставки стандартных 3-словных заголовков в события, в случае, если входящий поток
байтов или входящие датаграммы их не содержат.
Класс парсера должен реализовывать базовый интерфейс, объявленный в заголовке aclasses/semchan2.h
как абстрактный класс AChannelParser. Он содержит две виртуальные функции, которые надо
переопределить в проихводном классе:
virtual bool ParseStream( ChannelParserData& ) = 0;
virtual bool isStreamChannel( ){ return true; } // assume stream; for datagrams overidde to ‘false’
Второй из методов должен быть переопределен только для парсеров ориентированных на датаграммы,
и по умолчанию определен для потоковых парсеров. Первый метод ParseStream() собственно и реализует
разбор входного потока.
Обмен информацией с вызывающими функциями ParseStream() выполняет посредством структуры
ChannelParserData и возвращаемого значения. Структура ChannelParserData объявлена в том же
заголовочном файле следующим образом:
struct ChannelParserData // unified interface structure for parsers
{
// input data for parser
const char *data; // pointer to buffer with data
unsigned datasize; // size of remaining data in buffer (bytes)
bool endofstream; // true in case if no more data expected
// output data from parser
unsigned todo; // what channel has to do upon return: nothing/skip/enq+skip
unsigned skipbytes; // how many parsed bytes to skip within buffer (any ‘todo’ except ‘nothing’)
unsigned n2hoffset; // offset for n2h() conversion in buffer before enqueue (points to IEVT structure in buffer - see ChannelParser template)
unsigned enqoffset; // offset for enqueue
unsigned enqbytes; // how many bytes to enqueue
unsigned enqtype; // enqueue type: copy data only/prepend header/prepend ex-header
AgentEventHeaderEx header; // header/ex-header data, if needed (fill everything except ‘msgLen’ field)
public:
ChannelParserData( )
: data( 0 ), datasize( 0 ),
todo( ), skipbytes( ), n2hoffset( ), enqoffset( ), enqbytes( ), enqtype( ), header( )
{}
};
Первые три поля указывают на данные во входном буфере потока, и описывают длину доступных данных и
признак конца потока. Они являются входными параметрами парсера. Остальные поля парсер должен заполнить
и вернуть вызывающей функции. Есть возможность не заполнять остальные поля и просто вернуть значение
false как результат ParseStream(). В этом случае для потоковых каналов ничего не делается, и парсер
будет вызван еще раз с той же точки в потоке байт когда в буфере появится больше данных. Для
датаграммных протоколов возврат false приведет к потере датаграммы.
В случае, когда ParseStream() возвращает true, она должна заполнить остальные поля структуры
ChannelParserData, переданной ей в качестве параметра. Прежде всего, это поле todo, которое означает
что должна сделать с данными во входном буфере вызывающая функция:
- todoNothing ничего не делать (эквивалентно возврату false)
- todoSkipBytes пропустить ‘skipbytes’ байтов в потоке начиная с текущей точки (т.е. ‘data’)
- todoEnqueueAndSkip пропустить ‘skipbytes’ байтов в потоке и поставить в очередь автомата,
связанного с потоком, ‘enqbytes’ байтов из потока по смещению ‘enqoffset’
от текущей точки ‘data’. Перед постановкой применяется преобразование
n2h() входной структуры связанной с парсером по смещению ‘n2hoffset’.
При постановке события в очередь (только todoEnqueueAndSkip) также выполняются действие описанное полем
‘enqtype’ с данными взятыми из поля ‘header’, так что в последнем случае парсер должен также заполнить
эти поля. Значения поля ‘enqtype’:
- enqtStreamDataOnly копировать только данные потока в событие для автомата
- enqtPrependHeader вставить перед данными потока структуру AgentEventHeader, данные берутся
из поля ‘header’
- enqtPrependExHeader вставить перед данными потока структуру AgentEventHeaderEx, данные берутся
из поля ‘header’
Примечание: рекомендуется использовать только короткую структуру AgentEventHeader, расширенная
сохранена в библиотеке только по историческим причинам.
ОПРЕДЕЛЕНИЕ КЛАССОВ ДЛЯ КАНАЛОВ И ПАРСЕРОВ ПРИ КОДИРОВАНИИ АВТОМАТА
Для полного определения всех необходимых классов и структур канала и парсера, необходимо выполнить
следующую последовательность шагов (для примера используются некоторые имена классов):
1) определить структуры OEVT и IEVT, описывающие данные пересылаемые по каналу в обоих направлениях
(OEVT - для посылки, IEVT - для приема). Структуры не должны содержать виртуальных методов и
виртуальных базовых классов/структур. Структуры должны определять методы:
void h2n( );
void n2h( );
преобразующие порядок байтов в полях в сетевой из хостового и наоборот (хинт: использовать функции
из заголовка cclasses/hn2hn.h). Примечание: структуры IEVT, OEVT, и структура события в описании
автомата $Machine$/!EventStruct в общем случае могут быть разными структурами и не обязаны совпадать,
хотя IEVT может быть составной частью объединения, входящего в структуру события автомата;
2) Определить промежуточный базовый класс парсера, производный от класса AChannelParser и
переопределить в нем виртуальные методы ParseStream() и isStreamChannel() - второй только для
датаграммных каналов. Например, пусть наш канал - датаграммный (UDP) и датаграммы содержат
заголовок AgentEventHeader, тогда класс может выглядеть следующим образом:
class implDatagramChannelParser : public AChannelParser
{
public: // overidden
bool ParseStream( ChannelParserData& ); // parser implementation
bool isStreamChannel( ){ return false; } // datagram/packet channel
};
bool implDatagramChannelParser::!ParseStream( ChannelParserData& pardat )
{
pardat.todo = todoNothing; // todoNothing/todoSkipBytes/todoEnqueueAndSkip
if( pardat.data == 0 && pardat.datasize != 0 )return false;
pardat.todo = todoEnqueueAndSkip;
pardat.skipbytes = pardat.enqbytes = pardat.datasize;
pardat.n2hoffset = 0;
pardat.enqoffset = 0;
pardat.enqtype = enqtStreamDataOnly; // enqtStreamDataOnly/enqtPrependHeader/enqtPrependExHeader
return true;
}
3) Определить класс парсера, используя как базовый класс шаблон ChannelParser< IEVT, implParser>,
например:
class DatagramChannelParser : public ChannelParser< IEVT, implDatagramChannelParser >
{
};
4) Определить класс канала, используя как базовый класс шаблон AgentChannel< SEMThreadClass, OEVT >,
например:
class MyDatagramChannel : public AgentChannel< MachineThreadClass, OEVT >
{
public: // constructor
MyDatagramChannel( MachineThreadClass& agnt, AgentEventId_t eId, AgentInstanceId_t iId )
: AgentChannel< MachineThreadClass, OEVT >( "my_datagram_channel", agnt, _dg_parser, eId, iId ),
_dg_parser( )
{}
private: // internal data
DatagramChannelParser _dg_parser;
};
Все эти определения должны быть помещены в один заголовок, скажем, mydgchan.h, и имя этого заголовка
и имя класса должны использоваться в определении канала в директиве $Channels$.
Заголовочный файл aclasses/semchan2.h определяет некоторое количество шаблонов стандартных парсеров
для каналов, для которых ParseStream() дописывать уже не нужно (шаг 2 выше может быть пропущен).
Единственным аргументом этих шаблонов является тип структуры входных сообщений по каналу IEVT.
При их использовании надо иметь определенным тип IEVT и просто вывести свой производный класс
из шаблона с параметром IEVT, а также определить конструктор в производном классе.
Следующие шаблоны можно использовать:
- template< typename IEVT >class StreamChannelParser;
входной поток байт состоит из структур с включенными в них заголовками AgentEventHeader.
Структура IEVT должна включать AgentEventHeader.
- template< typename IEVT >class StreamChannelParserRedir;
то же самое, что предыдущий, но парсер при постановке событий в очередь к автоматам меняет
в заголовке событий ‘instance ID’ на другой, который задается аргументом конструктора данного
шаблона. Таким образом, этот парсер перенаправляет все события в некоторый заданный
экземпляр автомата.
- template< typename IEVT >class DatagramChannelParser;
парсер для датаграммного канала, входные датаграммы включают заголовок AgentEventHeader.
Структура IEVT должна включать AgentEventHeader.
- template< typename IEVT >class RawDatagramChannelParser;
парсер для датаграммного канала, входные датаграммы НЕ включают заголовок AgentEventHeader.
Структура IEVT НЕ должна включать AgentEventHeader. Конструктор имеет два аргумента - код
события и ‘instance ID’. Парсер при постановке в очередь к автомату вставляет заголовок
AgentEventHeader, и вписывает в него код события и номер экземпляра заданные аргументами
конструктора.
- template< typename IEVT >class DatagramChannelParserRemIpHdr;
аналогичен предыдущему, однако входные датаграммы содержат IP-заголовки (типичный пример
для Windows и Linux - raw socket c ICMP протоколом). Парсер удаляет IP заголовки и
вставляет AgentEventHeader при постановке в очередь к автомату. Структура IEVT НЕ должна
включать AgentEventHeader, а также должна описывать содержимое датаграммы без IP-заголовка.
ФАЙЛ КОНФИГУРАЦИИ
semcpp поддерживает чтение параметров каналов и таймеров из файла конфигурации, генерируя
соответствующий C код в конструкторе экземпляра и других методах. Доступ к файлу конфигурации
осуществляется через отдельный объект приложения, который должен обеспечивать интерфейс,
такой же, как описанный в заголовке cclasses/configx.h. Классы в библиотеке cclasses
из файлов config.* и configx.* реализуют доступ к конфигурационным файлам структуры
классических .ini файлов Windows, но semcpp внутреннее устройство этих файлов и объекта
конфигурации без разницы, ей нужна ссылка и интерфейс как у класса Config из configx.h.
Более точно, сгенерированный semcpp код может использовать следующие методы объекта
конфигурации:
void ReadIfNotReadYet( );
int IntPar ( const char *name, int defval = 0, const char *sect = 0 );
int IntParDec( const char *name, int defval = 0, const char *sect = 0 );
int IntParHex( const char *name, int defval = 0, const char *sect = 0 );
unsigned UnsignedPar ( const char *name, unsigned defval = 0, const char *sect = 0 );
unsigned UnsignedParDec( const char *name, unsigned defval = 0, const char *sect = 0 );
unsigned UnsignedParHex( const char *name, unsigned defval = 0, const char *sect = 0 );
const char *!CharPar( const char *name, const char *defval = "", const char *sect = 0 );
Доступ к объекту semcpp осуществляет используя информацию из строки ConfigFile директивы
$Machine$, где указывается ссылка на объект (или объявляется внешний extern объект конфигураци),
и заголовочный файл, описывающий ссылку (объект) и связанные с ней определений классов.
Чтобы включить параметр директивы $Timers$ или $Channels$ в файл конфигурации, нужно
в директиве пометить его восклицательным знаком после значения. Пример:
$Channels$
3 : eventListeningSocket, "listening socket event",
TcpServerListeningChannel, "tcplchan.h", tcp_server_channel, /* aux_constructor_args */,
auto, tcpserver, localhost!, 34567!, /* peer host /, / peer port */,
/* reconnect time /, / maxconn / 5!, / protocol /, / max send buffers */ 6!,
/* send low water mark / 4!, / send high water mark */ 1!,
/* receive buf size / , / receive buf remaining threshold */,
/* receive buf increment /, / receive buf max size */ ;
$End$
Сгенерированный semcpp код будет при создании экземпляра автомата читать значения параметров
из объекта конфигурации, если они там есть. Если нет, используется значение из директивы.
Также, semcpp в качестве подсказки включает в сгенерированный .cpp файл следующие комментарии
(пример для приведенной выше директивы $Channels$):
//
// the comment lines below describe config file parameters for this machine
//
// #tcp_server_channel_debug_level = 4
// #tcp_server_channel_host = "localhost"
// #tcp_server_channel_port = "34567"
// #tcp_server_channel_max_send_bufs = 6
// #tcp_server_channel_suspend_threshold = 4
// #tcp_server_channel_resume_threshold = 1
// #tcp_server_channel_max_connections = 5
//
Конфигурируемые параметры можно использовать для большинства параметров канала, включая
тип сокета и большую часть его числовых параметров, а также для интервалов времени таймеров.
Объект с именем config, объявленный в заголовочном файле configx.h, может использоваться следующим образом
(код используемый вокруг main() или в ее начале/конце):
=
const char *server_config_file[ ] = { // скписок путей, просматриваемый при поиске файла
"/etc/tcpserver.ini",
"./tcpserver.ini",
"../tcpserver.ini",
0
};
const char server_config_sect[ ] = "tcp_server"; // используемая секция файла
const char *server_agent_name = "tcp_server";
// при инициализации приложения
void ApplicationStartup( )
{
config.Construct( );
config.!SetMessagePrefix( server_agent_name ); // префикс для сообщений об ошибках
config.!SetConfigFileList( server_config_file ); // список путей
config.!SetConfigFileSection( server_config_sect ); // секция по умолчанию (когда ==0 в методах чтения параметров)
}
// перед завершением приложения
void ApplicationCleanup( )
{
config.Destruct( );
}
=