вторник, 20 февраля 2018 г.

Модель описания протокола

Меня беспокоит проблема описания протоколов. Мне приходится сталкиваться с протоколами обмена для электронного документа оборота. Диаграмма состоит из десятка состояний и переходы между состояниями обусловлены получением документа определенного типа. Диаграмма не учитывает ошибки и обработку ошибок. С другой стороны я пытаюсь осилить стандарт BACnet, там все описание стандарта пропитано статусными моделями. Только переходы и условия весьма сложны, их не описать таблицей. Если добавлять в статусную модель обработку ошибок -- она перестает быть наглядной и пугает, такое описание только путает.




Хочу сформулировать отдельно и подробно каждый из объектов, из которых строится описание работы службы.

Диаграмма состояний
Диаграмма состояний описывается множеством состояний, которое можно задать перечислением, типа
enum { ... };
Переходы по состояниям описываются таблицей. Для каждого состояния можно определить таблицу переходов.
{ переход в состояние состояние, условие перехода},
.  .  .
{ переход в состояние, безусловно}
Последний переход обозначает безусловный переход: переход выполняется либо в тоже состояние, либо в следующее. На каждом шаге мы обрабатываем один список, соответствующий текущему состоянию. Что такое "условие". В последний раз условие - это результат логической операции, мы договорились что условие - это логическая переменная, может быть представлена одним битом или целым числом. В архитектуре ARM Cortex-M для логических переменных есть механизм Bit-band alias. Условия переходов по состояниям рассчитываются оптом, перед анализом переходов. Переход по состояниям обычно представляют в виде
switch (state) {
case STATE: if (condition) { state = NEW_STATE;}
}
Это выглядит достаточно просто. Мне хочется донести мысль, что эта конструкция перестает быть наглядной и отлаживаемой, если внутрь блока switch(){...} вставлять вычисление логики и если внутрь вставлять обработку сценариев. На трех состояниях еще можно отследить логику, на каком цикле в каком состоянии обрабатывается сценарий, а вот дальше можно запутаться. Поэтому рекомендуется условия переходов вычислять заранее перед switch, и обработку сценариев устраивать вне конструкции switch. Я начал описание с табличного описания, переключился на структурированное. Если состояний достаточно много, то оптимальным является описание в виде таблицы. Хочу обратить внимание, что если предусмотрено несколько вариантов перехода из одного состояния в другие, то возникает явно или неявно приоритет (порядок) проверки условий перехода. Этот приоритет возникает из порядка обработки таблицы или порядка обработки ветвлений
switch (state) {
case STATE:
if (condition1) { state = STATE1;}
else if (condition2) {state = STATE2;}
else {state = STATE3;}
}

Сценарии или последовательность действий
При переходе из состояния в состояние, возникает необходимость выполнения последовательности действий. Обмен данными или выполнение процедуры. Назовем это сценарием. В данном случае это процедура обмена между приложением и службой или между клиентским и серверным приложением, между приложением и драйвером. Почему мы выделяем отдельный "сценарий", потому что сценарий после запуска имеет право жить своей жизнью. Конечный автомат определяет только момент запуска сценария. В сценарии излагается порядок действий. В конечном автомате тоже можно изложить порядок действий. Это выглядит как цепочка безусловных переходов между действиями. При этом каждое действие - это все равно действие, которое обрабатывается по случаю перехода. Так мы сильно перегружаем диаграмму состояний. Поэтому выношу цепочки действий в сценарии, в отдельную сущность (см. командный объект). Теперь надо вернуться к частному случаю, я пытаюсь описать любой сетевой протокол в общем виде.

Описание сценария обмена
Я нашел подходящий язык. Периодически на этом языке пытаются описывать протоколы в общем виде. Язык состоит из четырех сервисных примитивов: Request -- запрос, Indication -- уведомление, Response -- отклик, Confirm -- подтверждение. Этими четырьмя словами можно описать процедуры, из которых состоит обмен приложения и сервис-провайдера (службы, драйвера, SAP).
Классификация сценариев может быть продолжена на протоколы, которые требуют подтверждения и не требуют подтверждения (Confirmed/Unconfirmed Application Services).
Для протоколов, которые не требуют подтверждения получения остается два типа примитивов: Request -- запрос на передачу данных и Indication -- уведомление о получении данных.
Все примитивы мы будем рассматривать с точки зрения приложения, а не сервис-провайдера (службы, драйвера).

Сервис без подтверждения (Unconfirmed Application Service)
С точки зрения приложения, со стороны приложения, мы ожидаем выставления события типа Indication. Процедура ожидания, проверки события и получения данных называется словом уведомление. Приложение ожидает уведомления о готовности данных. Процесс общения между приложением и сервис-провайдером можно основывать на сигналах, см osSignalWait().
Request -- запрос отсылается без подтверждения и без блокировки, отсылка запроса не вызывает задержки на обработку.

Сервис с подтверждением (Confirmed Application Service)
Общение происходит парами Request -- запрос требует подтверждения получения -- Confirm, получение сообщения методом Indication -- уведомлением требует ответа методом Response. Отличие состоит в порядке применения функций ожидания события и отсылки запроса. По сути функция Confirm и Indication не отличается и сводится к ожиданию события. Однако, Confirm ссылается на сообщение, на которое происходит ответ. Indication не ссылается на сообщение, только на сессию, в ответ получает ссылку на сообщение. Такое же различие в операциях Request и Response, Request не ссылается на сообщение, а Response ссылается. В итоге наших рассуждений пришли в двум параметрам: дескриптор сессии - идентификатор процесса, и идентификатор пакета. --- это какая-то ересь! надо зачеркнуть весь абзац.

Начнем заново:
Сервис принимает и отсылает сообщения. Если сервис -- это функция, то она ждет событие - уведомление о получении данных - Indication. Служба оживает и начинает процесс обработки и доставки сообщения. Вот суть. Возможно служба сама в состоянии сформировать ответ на сообщение, тогда это называется словом Request. Если служба не способна сама ответить и ей надо запросить у другого сервиса, то это называется словом Request, Response - так будет называться ответ - уведомление на получение. Когда служба передает ответ ниже использует тип запроса Request. То что приходит на Request, называется подтверждение отправки - Confirm. Короче, как не пытаюсь изложить мысль, получается плохо.

Давайте говорить о примитивах общения между службами (третья попытка), каждый примитив характеризуется своим набором данных (аргументов).  Indication -- это то что служба ждет, уведомление. Уведомление содержит пакет необработанных данных. Response -- это то что служба ждет от приложения (свыше), содержит пакет обработанных данных, которые следует передать ниже, "в сеть", в нижестоящую сервисную службу. Confirm -- это такой вид уведомления о доставке, который должен ссылаться на запрос. Request -- это запрос который происходит по инициативе службы или приложения. Это только я вижу всю бессмысленность описания?
Четвертая попытка. Все запросы можно оценивать по отношению к функции. Наша служба ждет с таймаутом, ждет сообщения нескольких типов. Типы бывают: Indication, Response, ... А-А-А бесы одолевают!

Специальный вид уведомления, уведомление о завершении операции
По завершении операции можно ожидать подтверждение с кодом завершения операции. Специальная форма Indication доставляет уведомление с кодом завершения операции. Приложение точно также ожидает готовности службы, и получает в ответ.
Тут, чтобы не путаться, надо понимать, что уведомление готовности формирует сервис-провайдер, а не служба которой адресован пакет. Например в такой форме можно получить ответ от драйвера, что сеть не доступна. Или в такой форме можно получить уведомление, что интерфейс доступен для отсылки данных. Эти сообщения мы будем получать как REPORT.Indication. ABORT.Indication, REJECT.Indication и т.п. Причем приставка будет означать формат данных и тип сообщения.

Привязка к сигналам
Каждый тип и направление сообщения мы считаем отдельным флагом в сигналах службы. Нужна функция привязки событий службы к битам в слове сигналов службы.

Zero-copy concept
В общую кучу добавлю технологию передачи данных между приложением и сервисом без копирования из памяти приложения в память службы. При запросе служба работает с тем же буфером, который заполняет приложение. В случае Confirmed Service приложение в результате Indication получает буфер данных от службы. Для буфера данных нужно определить операцию Unref, которая "освобождает" буфер от блокировки одновременного доступа. Сразу возникает вопрос, кому принадлежит память: приложению, службе или системе. Для обеспечения концепции нужен менеджер памяти. Концепт хорошо расширяется на связку запрос-ответ (Indication-Response). Заполнять ответ на запрос бывает удобно в том же буфере.

Служебный таймер
Еще один фактор -- время. Некоторые переходы могут происходить по тайм ауту (по времени). Для этого в состав объекта "конечный автомат" или "сервис" можно включать таймер или штамп времени последнего события. Таймер --  это свойство сервиса. таймер можно запускать с заданным временным интервалом (таймаутом) или останавливать.

Сценарии
Поддержку сценариев хочется развить и сделать удобный API для сценариев. Сценарий обмена данных -- это диаграмма. Представить ее можно конечным автоматом, но не уверен что это эффективно. Сценарий - это последовательность общения. Бывает наступает такое событие, когда надо запустить активность сервиса. Сервис сам начинает рассылать данные, принимать сообщения. Мы различаем два состояния сервиса: Requesting и Responding. Состояние Requesting - сервис рассылает запросы и обрабатывает данные в соответствии со сценарием, Responding - сервис обрабатывает запросы. Пока что у меня на входе две статусные модели - одна для состояния Requesting, а вторая для Responding. При том что это сервис - это одна функция, я не понимаю, как это эффективно совместить. Простые сценарии удобно выражать структурами типа switch(){case}. Пока что для обозначения сценария предлагаю выделить механизм, некоторое событие может вызвать исполнение последовательностной диаграммы. Последовательнсотная диаграмма - это набор функций связанных в цепочку или кольцо. Каждая функция запускается условно, по событию с таймаутом. Когда мы говорим про службу в состоянии Requesting, мы говорим о том, что служба и есть одна такая функция, которая сама себя запускает с разными таймаутами и с разными условиями (событиями) ожидания. Служба зациклена на себя. Тему надо развить.

Обработка исключений в конечном автомате
Я не знаю, как в общем виде описывать обработку исключений. Когда я смотрю на описание конечного автомата в стандарте BACnet я вижу описание сильно перегружено обработкой исключений. Сам по себе сервис очень прост, если в него не добавлять сегментацию пакетов, обработку ошибок и таймаутов. Много времени трачу на понимание -- сначала анализ состояния, потом обработка исключений, или наоборот, сначала исключения потом анализ состояния. При вызове функции сервиса получаем равные условия можем начать с анализа таймаута, внутри анализировать состояния, можем начать с анализа типа вызова Indication или Response, внутри все остальное. В итоге я пишу как проще, пишу сначала без исключений и ошибок, потом поверх структуры пишу исключения. Путь для разработки не самый короткий. И проблема в том что я отхожу от описания стандарта, делаю что-то свое.




{07/2016, 02/2018}

Комментариев нет:

Отправить комментарий