Содержание
Пример echo
Пример echo демонстрирует использование IPC-транспорта.
Показана работа с основными инструментами, позволяющими реализовать взаимодействие между сущностями.
О примере echo
Пример echo описывает простейший случай взаимодействия двух сущностей:
- Сущность
Client
передает сущностиServer
число (value
). - Сущность
Server
изменяет это число и передает новое число (result
) сущностиClient
. - Сущность
Client
выводит числоresult
на экран.
Чтобы организовать такое взаимодействие сущностей, потребуется:
- Соединить сущности
Client
иServer
, используя init-описание. - Реализовать на сервере интерфейс с единственным методом
Ping
, который имеет один входной аргумент – исходное число (value
) и один выходной аргумент – измененное число (result
).Описание метода
Ping
на языке IDL:Ping(in UInt32 value, out UInt32 result);
- Создать файлы статических описаний на языках EDL, CDL и IDL. С помощью компилятора NK сгенерировать файлы, содержащие транспортные методы и типы (прокси-объект, диспетчеры и т.д.).
- В коде сущности
Client
инициализировать все необходимые объекты (транспорт, прокси-объект, структуру запроса и др.) и вызвать интерфейсный метод. - В коде сущности
Server
подготовить все необходимые объекты (транспорт, диспетчер компонента и диспетчер сущности и др.), принять запрос от клиента, обработать его и отправить ответ.
Пример echo состоит из следующих исходных файлов:
client/src/client.c
– реализация сущностиClient
;server/src/server.c
– реализация сущностиServer
;resources/Server.edl
,resources/Client.edl
,resources/Ping.cdl
,resources/Ping.idl
– статические описания;init.yaml
– init-описание.
Реализация сущности Client в примере echo
В коде сущности Client
используются транспортные типы и методы, которые будут сгенерированы во время сборки решения компилятором NK на основе IDL-описания интерфейса Ping
.
Однако чтобы получить необходимые для реализации сущности описания типов и сигнатуры методов, вы можете воспользоваться компилятором NK непосредственно после создания EDL-описания сущности, CDL-описаний компонентов и IDL-описаний используемых интерфейсов взаимодействия. В результате необходимые типы и сигнатуры методов будут объявлены в сгенерированных файлах *.h
.
В реализации сущности Client
необходимо:
- Получить клиентский IPC-дескриптор соединения (канала), используя функцию локатора сервисов
ServiceLocatorConnect()
.На вход нужно передать имя IPC-соединения
server_connection
, заданное ранее в файле init.yaml. - Инициализировать транспорт
NkKosTransport
, передав полученный IPC-дескриптор в функциюNkKosTransport_Init()
. - Получить идентификатор необходимого интерфейса (
RIID
), используя функцию локатора сервисовServiceLocatorGetRiid()
. - На вход нужно передать полное имя реализации интерфейса
Ping
. Оно состоит из имен экземпляра компонента (Echo.ping
) и интерфейса (ping
), заданных ранее в Server.edl и Ping.cdl. - Инициализировать прокси-объект, передав транспорт и идентификатор интерфейса в функцию
Ping_proxy_init()
. - Подготовить структуры запроса и ответа.
- Вызвать интерфейсный метод
Ping_Ping()
, передав прокси-объект, а также указатели на запрос и ответ.
client.c
/* Файлы, необходимые для инициализации транспорта. */
/* Описание интерфейса сервера, по которому обращается клиентская сущность. */
/* Точка входа в клиентскую сущность. */
int main(int argc, const char *argv[])
{
NkKosTransport transport;
struct echo_Ping_proxy proxy;
int i;
fprintf(stderr, "Hello I'm client\n");
/* Получаем клиентский IPC-дескриптор соединения с именем
* "server_connection". */
Handle handle = ServiceLocatorConnect("server_connection");
assert(handle != INVALID_HANDLE);
/* Инициализируем IPC-транспорт для взаимодействия с серверной сущностью. */
NkKosTransport_Init(&transport, handle, NK_NULL, 0);
/* Получаем Runtime Interface ID (RIID) для интерфейса echo.Ping.ping.
* Здесь ping – имя экземпляра компонента echo.Ping,
* echo.Ping.ping - имя реализации интерфейса Ping. */
nk_iid_t riid = ServiceLocatorGetRiid(handle, "echo.Ping.ping");
assert(riid != INVALID_RIID);
/* Инициализируем прокси-объект, указав транспорт (&transport)
* и идентификатор интерфейса сервера (riid). Каждый метод
* прокси-объекта будет реализован как отправка запроса серверу. */
echo_Ping_proxy_init(&proxy, &transport.base, riid);
/* Структуры запроса и ответа */
echo_Ping_Ping_req req;
echo_Ping_Ping_res res;
/* Тестовый цикл. */
req.value = EXAMPLE_VALUE_TO_SEND;
for (i = 0; i < 10; ++i)
{
/* Вызываем интерфейсный метод Ping.
* Серверу будет отправлен запрос для вызова метода Ping интерфейса
* ping_comp.ping_impl с аргументом value. Вызывающий поток блокируется
* до момента получения ответа от сервера. */
if (echo_Ping_Ping(&proxy.base, &req, NULL, &res, NULL) == rcOk)
{
/* Выводим значение result, содержащееся в ответе
* (result - выходной аргумент метода Ping). */
fprintf(stderr, "result = %d\n", (int) res.result);
/* Помещаем полученное значение result в аргумент value
* для повторной отправки серверу в следующей итерации. */
req.value = res.result;
}
else
fprintf(stderr, "Failed to call echo.Ping.Ping()\n");
}
return EXIT_SUCCESS;
}
Реализация сущности Server в примере echo
В коде сущности Server
используются транспортные типы и методы, которые будут сгенерированы во время сборки решения компилятором NK на основе EDL-описания сущности Server
.
Однако чтобы получить необходимые для реализации сущности типы и сигнатуры методов, вы можете воспользоваться компилятором NK непосредственно после создания EDL-описания сущности, CDL-описаний компонентов и IDL-описаний используемых интерфейсов взаимодействия. В результате необходимые типы и сигнатуры методов будут объявлены в сгенерированных файлах *.h
.
В реализации сущности server
необходимо:
- Реализовать метод
Ping()
.Сигнатура реализации метода
Ping()
должна в точности совпадать с сигнатурой интерфейсного методаPing_Ping()
, который объявлен в файле Ping.idl.h. - Получить серверный IPC-дескриптор соединения (канала), используя функцию локатора сервисов
ServiceLocatorRegister()
.На вход нужно передать имя IPC-соединения
server_connection
, заданное ранее в файле init.yaml. - Инициализировать транспорт
NkKosTransport
, передав полученный IPC-дескриптор в функциюNkKosTransport_Init()
. - Подготовить структуры запроса и ответа.
- Инициализировать dispatch-метод (диспетчер) компонента
Ping
, используя функциюPing_component_init()
. - Инициализировать dispatch-метод (диспетчер) сущности
Server
, используя функциюServer_entity_init()
. - Получить запрос, вызвав
nk_transport_recv()
. - Обработать полученный запрос, вызвав диспетчер
Server_entity_dispatch()
.Диспетчер вызовет необходимую реализацию метода на основе полученного от клиента идентификатора интерфейса (
RIID
). - Отправить ответ сущности
Client
, вызвавnk_transport_reply()
.
server.c
/* Файлы, необходимые для инициализации транспорта. */
/* Описания сущности-сервера на языке EDL. */
/* Тип объекта реализующего интерфейс. */
typedef struct IPingImpl {
struct echo_Ping base; // базовый интерфейс объекта
int step; // дополнительные параметры
} IPingImpl;
/* Реализация метода Ping. */
static nk_err_t Ping_impl(struct echo_Ping *self,
const echo_Ping_req *req,
const struct nk_arena* req_arena,
echo_Ping_res* res,
struct nk_arena* res_arena)
{
IPingImpl *impl = (IPingImpl *)self;
/* Значение value, пришедшее в запросе от клиента, инкрементируем на
* величину шага step и помещаем в аргумент result, который будет
* отправлен клиенту в составе ответа от сервера. */
res->Ping.result = req->Ping.value + impl->step;
return NK_EOK;
}
/* Конструктор объекта IPing.
* step - шаг, то есть число, на которое будет увеличиваться входящее значение. */
static struct echo_Ping *CreateIPingImpl(int step)
{
/* Таблица реализаций методов интерфейса IPing. */
static const struct echo_Ping_ops ops = {
.Ping = Ping_impl
};
/* Объект, реализующий интерфейс. */
static struct IPingImpl impl = {
.base = {&ops}
};
impl.step = step;
return &impl.base;
}
/* Точка входа в сервер. */
int main(void)
{
NkKosTransport transport;
ServiceId iid;
/* Получаем серверный IPC-дескриптор соединения "server_connection". */
Handle handle = ServiceLocatorRegister("server_connection", NULL, 0, &iid);
assert(handle != INVALID_HANDLE);
/* Инициализируем транспорт до клиента. */
NkKosTransport_Init(&transport, handle, NK_NULL, 0);
/* Подготавливаем структуры запроса к сущности server: фиксированную
* часть и арену. Поскольку ни у одного из методов сущности server нет
* аргументов типа sequence, используются только фиксированные части
* запроса и ответа. Арены фактически не используются. Однако в серверные
* методы транспорта (nk_transport_recv, nk_transport_reply) и
* dispatch-метод server_entity_dispatch необходимо передать валидные
* арены запроса и ответа. */
echo_Server_entity_req req;
char req_buffer[echo_Server_entity_req_arena_size];
struct nk_arena req_arena = NK_ARENA_INITIALIZER(req_buffer, req_buffer + sizeof(req_buffer));
/* Подготавливаем структуры ответа: фиксированную часть и арену. */
echo_Server_entity_res res;
char res_buffer[echo_Server_entity_res_arena_size];
struct nk_arena res_arena = NK_ARENA_INITIALIZER(res_buffer, res_buffer + sizeof(res_buffer));
/* Инициализируем диспетчер компонента ping. 3 – величина шага,
* то есть число, на которое будет увеличиваться входящее значение. */
echo_Ping_component component;
echo_Ping_component_init(&component, CreateIPingImpl(3));
/* Инициализируем диспетчер сущности server. */
echo_Server_entity entity;
echo_Server_entity_init(&entity, &component);
fprintf(stderr, "Hello I'm server\n");
/* Реализация цикла обработки запросов. */
do
{
/* Сбрасываем буферы с запросом и ответом. */
nk_req_reset(&req);
nk_arena_reset(&req_arena);
nk_arena_reset(&res_arena);
/* Ожидаем поступление запроса к сущности-серверу. */
if (nk_transport_recv(&transport.base, &req.base_, &req_arena) != NK_EOK) {
fprintf(stderr, "nk_transport_recv error\n");
} else {
/* Обрабатываем полученный запрос, вызывая реализацию Ping_impl
* запрошенного интерфейсного метода Ping. */
echo_Server_entity_dispatch(&entity, &req.base_, &req_arena, &res.base_, &res_arena);
}
/* Отправка ответа. */
if (nk_transport_reply(&transport.base, &res.base_, &res_arena) != NK_EOK) {
fprintf(stderr, "nk_transport_reply error\n");
}
}
while (true);
return EXIT_SUCCESS;
}
Файлы описаний в примере echo
Описание сущности Client
Сущность Client
не реализует ни одного интерфейса, поэтому в ее EDL-описании достаточно объявить имя сущности:
Client.edl
/* Описание сущности Client. */
entity echo.Client
Описание сущности Server
Описание сущности Server
должно содержать информацию о том, что он реализует интерфейс Ping
. С помощью статических описаний нужно "поместить" реализацию интерфейса Ping
в новый компонент (например, Ping
), а экземпляр этого компонента – в сущность Server
.
Сущность Server
содержит экземпляр компонента Ping
:
Server.edl
/* Описание сущности Server. */
entity echo.Server
/* Server - именованный экземпляр компонента echo.Ping. */
components {
Server: echo.Ping
}
Компонент Ping
содержит реализацию интерфейса Ping
:
Ping.cdl
/* Описание компонента Ping. */
component echo.Ping
/* ping - реализация интерфейса Ping. */
interfaces {
ping: echo.Ping
}
Пакет Ping
содержит объявление интерфейса Ping
:
Ping.idl
/* Описание пакета Ping. */
package echo.Ping
interface {
Ping(in UInt32 value, out UInt32 result);
}
Init-описание
Чтобы сущность Client
могла вызвать метод сущности Server
, между ними требуется создать соединение (IPC-канал).
Для этого в init-описании укажите, что необходимо запустить сущности Client
и Server
, а также соединить их:
init.yaml
entities:
- name: echo.Client
connections:
- target: echo.Server
id: server_connection
- name: Server
Поскольку сущность Server
указана как - target
, она будет выступать в роли серверной сущности, т.е. принимать запросы сущности Client
и отвечать на них.
Созданный IPC-канал имеет имя server_connection
. С помощью локатора сервисов можно получить дескриптор этого канала.
Сборка и запуск примера echo
См. "Сборка и запуск примеров".
Схема сборки примера echo выглядит следующим образом: