Введение
Как я уже писал ранее, в данный момент я работаю с отечественной СУБД Tarantool, которая, по совместительству, является еще и сервером приложений. Некоторое время назад я уже писал как можно создавать приложения для Tarantool на языке Lua, а также про построение регистронезависимого индекса в нем.
Несмотря на то, что Lua является основным языком создания модулей для Tarantool их можно создавать еще и на C (с недавнего времени еще и на Rust с помощью Tarantool Rust SDK). К сожалению, в Интернете очень мало инфромации по поводу создания C модулей для Tarantool и этой статьей я собираюсь чуть-чуть заполнить этот пробел.
Описание задачи
В качестве задачи для создания модуля, я взял частичную реализацию протокола EGTS, о котором я писал ранее. Она будет сильно упращенной и будет происходить разбор только секции телеметрических данных.
Каждый клиент будет работать в своем файбере(fiber). Fiber - это набор инструкций, который исполняется с помощью кооперативной многозадачности в Tarantool.
Создание модуля
В создании модуля нам поможет modulekit. Для начала разработки его нужно загрузить
git clone https://github.com/kuznetsovin/tarantool-modulekit.git
git fetch origin
git checkout -b ckit origin/ckit
После этого нужно заменить все вхождения ckit
(в именах файлов и в их содержимом) на название нашего модуля, в моем случае это egts
.
В итоге получилось что-то такое:
├── egts
│ ├── CMakeLists.txt
│ ├── init.lua
│ ├── lib.c
├── test
│ └── egts_test.lua
├── egts-scm-1.rockspec
├── CMakeLists.txt
├── LICENSE
├── Makefile
└── README.md
Основной код библиотеки будет хранитс в файле lib.c. Весь код я не буду описывать, а опишу только важные функции.
Основная функция для загрузки нашей библиотеки это luaopen_egts_lib
:
LUA_API int
luaopen_egts_lib(lua_State *L)
{
...
static const struct luaL_Reg lib [] = {
{"start_server", start_server},
{"stop_server", stop_server},
{NULL, NULL}
};
luaL_newlib(L, lib);
return 1;
}
Здесь происходит описание того, какие функции из нашего модуля будут доступны к вызову из Lua. В нашем случае это start_server
и stop_server
. Функции добавляются в специальную структуру luaL_Reg
, которая потом регистрируется в Lua с помощью функции luaL_newlib
. Структура lua_State
хранит всю информацию о состоянии исполняющегося Lua потока.
Взаимодействие с lua происходит как работа со стековой машиной, то есть за счет операций добавления на стек и извлечения со стека. Поэтому функции которые будут вызываться из Lua, должны иметь в качестве входного аргумента указатель на lua_State.
Для пример разберем функцию start_server
:
static int
start_server(lua_State *L)
{
if (lua_gettop(L) < 1)
return luaL_error(L, "usage: start_server(port: number)");
int port = lua_tointeger(L, 1);
/*
socket open code
...
*/
f_egts_srv = fiber_new("egts_server", fiber_conn_listner);
fiber_start(f_egts_srv, sock_srv);
say_info("start egts server on port %d", port);
return 0;
}
В начале данной функци провкряется есть ли на стеке значение. Для этого используется функция lua_gettop
которая возвращает индекс верхнего аргумента стека, и если результат меньше одного значит значений нет.
Затем мы считываем значение со стека с помощью функции lua_tointeger
, как можно понять из названия переменной, оно используется для открытия сетевого порта. После этого идет код окртыия сетевого сокета, который я опустил для краткости.
В конце функции создается файбер, с помощью функции fiber_new
, который будет слушать наш открытый сокет. Файбер в данном случае используются, как аналог “зеленых потоков” и их управлением занимается не операционная система а tarantool runtime .
Остальное C код я разбирать не буду, так как к теме статьи он не имеет отношения.
Далее файле init.lua
можно добавить обертки для более удобного вызова функций сторонним приложением, или какие-то вспомагательные функции:
local c_egts = require('egts.lib')
...
return {
SYSTEM_SPACE = _SYSTEM_SPACE,
init_store = init_store,
start_server = c_egts.start_server;
stop_server = c_egts.stop_server;
}
Обертки start/stop server нужны для чтобы во внешнем приложении не использовать require('egts.lib')
для их вызова, а просто наприсать require('egts')
, что более эстетично и не несет лишней смыславной нагрузки для внешнего пользователя.
Сборка модуля
Для сборки модуля нужно выполнить команду:
tarantoolctl rocks make
которая соберет модуль в папку .rosks
локальной директории
Работа с модулем из Tarantool
Для работы с модулем достачно его импортировать в приложении:
local egts = require('egts')
egts.init_store()
egts.start_server(5555)
Заключение
Как видно из статьи при создании C модуля для Tarantool есть свои особенности, но в целом большой сложности в этом нет. Исходный код модуля, описанного в статье, можно найти на Github.
Также нужно отметить, что на основе этого модуля я проводил защиту своего курсового проекта на OTUS: