LeshaInc 625 Опубликовано: 22 июня, 2016 (изменено) Если вы когда нибудь сталкивались с созданием серверов под OpenOS, вам знакома проблема блокирования всего компьютера. Его нельзя использовать, пока сервер не выключится. Некоторые придумывали свои велосипеды, которые запускали event.listen или event.ignore. Но не то это! Для таких вещей существует rc, в OpenOS. Сейчас я расскажу вам, что это за зверь, где он живет и зачем он нам. Немного теории Итак, все скрипты для rc хранятся в папочке /etc/rc.d/. Любой файл с расширением .lua, который находится там, является rc-скриптом.У такого скрипта все глобальные функции это команды. Запустить такую команду можно командой rc <имя скрипта, без расширения> <имя команды>. Например если мы создадим глобальную функцию start в файлике /etc/rc.d/test.lua, то запустив команду rc test start эта команда выполнится. Все просто. Получить список команд определенного скрипта можно при помощи команды rc <имя скрипта, без расширения>. Например так: rc test. RC сам создает некоторые команды, хотя мы их можем переопределить: enable/disable. Соответственно включают/выключают скрипт. Все включенные скрипты получат команду start при запуске компьютера. restart если есть start и stop. Перезапускает скрипт, то есть поочередно запускает stop и start. Каждый скрипт можно конфигурировать. Конфиг находится в /etc/rc.cfg.Формат файла прост: <имя скрипта> = <конфиг: все что угодно, таблица, текст, число> По сути это обычный луа-файл, все глобальные значения из которого считаются полями. Каждый скрипт может получить доступ к своему конфигу. Конфиг записывается в переменную args. С теорией покончено, приступим к практике! Практика Я предлагаю написать простой эхо-сервер. Он будет слушать определенный порт (из конфига), и отвечать на любое сообщение эхом. Начнем! Создадим файл /etc/rc,d/echo.lua, и начнем писать в нем код. Нам понадобятся некоторые переменные -- нам понадобятся библиотека event -- что бы установить свой обработчик событий local event = require("event") -- еще нам нужна библиотека computer -- что бы подключить компонент `modem` local component = require("component") -- будем считать количество ответов -- зачем? для диванных аналитиков конечно! local count = 0 -- будем хранить текущее положение -- что б никто не запустил случайно сервер два раза local started = false -- сюда запишем прокси модема, когда убедимся что он есть local modem -- а сюда запишем рабочий порт local port Создадим обработчик события modem_message. Он будет вызываться при каждом сообщении по сетевой карте. -- функция ниже будет запускаться -- при каждом сообщении по сетевой плате local function onModemMessage(_, _, snd, prt, _, ...) -- `_` в нашем понимании - неиспользуемый аргумент -- если порт сообщения не совпадает с портом -- из конфига, выходим print(prt) if prt ~= port then return end -- добавим еденицу к счетчику count = count + 1 -- здесь мы уверены, что modem существует -- так как обработчик поставится только если у нас есть модем modem.send(snd, prt, ...) -- / | \ -- получатель, порт, данные -- просто напросто пересылаем отправителю то что он -- отправил нам =) end Теперь мы добавим команду start, которая запустит наш сервер. -- функция ниже глобальная, `local` нет. -- потому _rc_ ее будет смело считать командой -- а команда эта будет запускать наш сервер function start() -- мы должны проверить, есть ли у нашего сервера -- сетевая карта if not component.isAvailable("modem") then -- если ее нет, мы выводим ошибку и выходим io.stderr:write("Сетевая карта не найдена!") return end -- еще нам нужно проверить, выключены ли мы -- если это не так, снова ошибка if started then io.stderr:write("Сервер уже запущен!") return end count = 0 -- сбросим счетчик started = true -- теперь мы знаем, что с этого момента -- сервер включен. port = args or 666 -- args это переменная, в которую _rc_ запишет данные из конфига -- конфиг находится в /etc/rc.cfg -- но если в конфиге порт не настроен, используем дефолтный modem = component.modem -- мы проверили, что модем существует, -- поэтому смело его подключаем -- откроем порт modem.open(port) -- нам нужно сделать так, что бы все сообщения -- по сетевой карте обслуживались нашей функцией event.listen("modem_message", onModemMessage) -- ну а теперь мы точно включились! end Куда без команды stop? Добавим ее! -- эта функция тоже глобальная, поэтому считаем ее командой -- эта команда остановит сервер function stop() -- мы должны проверить, включен ли вообще сервер -- если он не включен, какой толк его выключать, верно? if not started then -- если он не включен, выводим ошибку и выходим io.stderr:write("Сервер уже выключен!") return end count = 0 -- опять же сбрасываем счетчик started = false -- запоминаем, что теперь -- сервер выключен -- теперь нам нужно по-настоящему выключить сервер event.ignore("modem_message", onModemMessage) -- все! с этого момента сервер выключен, и не принимает сообщение end Еще самая малость, добавим команду printCount, которая будет отображать количество полученных сообщений. -- и это тоже команда, думаю вы понимаете -- а эта команда напишет количество отправленных сообщений function printCount() -- выводить информацию будем только если сервер не запущен -- поэтому выведем ошибку, если он не включен if not started then io.stderr:write("Сервер выключен!") return end print(count) -- пишем количество подключений -- диванные аналитики ликуют! end Ну вот и все, программа сделана, диванные аналитики ликуют, инженеры недоумевают. Полный код: -- нам понадобятся библиотека event -- что бы установить свой обработчик событий local event = require("event") -- еще нам нужна библиотека computer -- что бы подключить компонент `modem` local component = require("component") -- будем считать количество ответов -- зачем? для диванных аналитиков конечно! local count = 0 -- будем хранить текущее положение -- что б никто не запустил случайно сервер два раза local started = false -- сюда запишем прокси модема, когда убедимся что он есть local modem -- а сюда запишем рабочий порт local port -- функция ниже будет запускаться local function onModemMessage(_, _, snd, prt, _, ...) -- `_` в нашем понимании - неиспользуемый аргумент -- если порт сообщения не совпадает с портом -- из конфига, выходим print(prt) if prt ~= port then return end -- добавим еденицу к счетчику count = count + 1 -- здесь мы уверены, что modem существует -- так как обработчик поставится только если у нас есть модем modem.send(snd, prt, ...) -- / | \ -- получатель, порт, данные -- просто напросто пересылаем отправителю то что он -- отправил нам =) end -- функция ниже глобальная, `local` нет. -- потому _rc_ ее будет смело считать командой -- а команда эта будет запускать наш сервер function start() -- мы должны проверить, есть ли у нашего сервера -- сетевая карта if not component.isAvailable("modem") then -- если ее нет, мы выводим ошибку и выходим io.stderr:write("Сетевая карта не найдена!") return end -- еще нам нужно проверить, выключены ли мы -- если это не так, снова ошибка if started then io.stderr:write("Сервер уже запущен!") return end count = 0 -- сбросим счетчик started = true -- теперь мы знаем, что с этого момента -- сервер включен. port = args or 666 -- args это переменная, в которую _rc_ запишет данные из конфига -- конфиг находится в /etc/rc.cfg -- но если в конфиге порт не настроен, используем дефолтный modem = component.modem -- мы проверили, что модем существует, -- поэтому смело его подключаем -- откроем порт modem.open(port) -- нам нужно сделать так, что бы все сообщения -- по сетевой карте обслуживались нашей функцией event.listen("modem_message", onModemMessage) -- ну а теперь мы точно включились! end -- эта функция тоже глобальная, поэтому считаем ее командой -- эта команда остановит сервер function stop() -- мы должны проверить, включен ли вообще сервер -- если он не включен, какой толк его выключать, верно? if not started then -- если он не включен, выводим ошибку и выходим io.stderr:write("Сервер уже выключен!") return end count = 0 -- опять же сбрасываем счетчик started = false -- запоминаем, что теперь -- сервер выключен -- теперь нам нужно по-настоящему выключить сервер event.ignore("modem_message", onModemMessage) -- все! с этого момента сервер выключен, и не принимает сообщение end -- и это тоже команда, думаю вы понимаете -- а эта команда напишет количество отправленных сообщений function printCount() -- выводить информацию будем только если сервер не запущен -- поэтому выведем ошибку, если он не включен if not started then io.stderr:write("Сервер выключен!") return end print(count) -- пишем количество подключений -- диванные аналитики ликуют! end Время QA! Симулируем Васю Пупкина... А вот уже на другом компьютере отправлено сообщение на работающий сервер. Счетчик тоже работает. Установим значение в конфиге. Проверим... Все работает, инженеры ликуют! Такую систему очень удобно использовать для всяческих серверов. Сервер может спокойно работать в фоне, а в главном потоке спокойно можно запускать консоль сервера, интерпретатор луа, rm -rf / ... Enjoy! Изменено 22 июня, 2016 пользователем LeshaInc 9 2 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
qwertyMAN 1 722 Опубликовано: 22 июня, 2016 event.listen forever Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
NEO 541 Опубликовано: 22 июня, 2016 Весь код в спойлеры надо добавить. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
LeshaInc Автор темы 625 Опубликовано: 22 июня, 2016 event.listen forever Это точно. Весь код в спойлеры надо добавить. Со спойлерами пропадает весь запах "крутого туториала". А еще пропадает юзабилити, надо тыкать на эти кнопуськи "Открыть", не удобно ИМХО. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Totoro 3 563 Опубликовано: 22 июня, 2016 Весь код в спойлеры надо добавить. Имхо спойлеры эти выглядят фигово в статьях. Плюс, это туториал. Код тут такой же элемент статьи как и текст. Читаешь текст, читаешь код. Получаешь комплексное впечатление. А необходимость постоянно тыкать на спойлеры будет раздражать. Ну и наконец, фрагменты кода просто визуально разбавляют унылую стену текста. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
NEO 541 Опубликовано: 22 июня, 2016 Имхо спойлеры эти выглядят фигово в статьях. Плюс, это туториал. Код тут такой же элемент статьи как и текст. Читаешь текст, читаешь код. Получаешь комплексное впечатление. А необходимость постоянно тыкать на спойлеры будет раздражать. Ну и наконец, фрагменты кода просто визуально разбавляют унылую стену текста. Это точно. Со спойлерами пропадает весь запах "крутого туториала". А еще пропадает юзабилити, надо тыкать на эти кнопуськи "Открыть", не удобно ИМХО. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 154 Опубликовано: 23 июня, 2016 Весь код в спойлеры надо добавить.В этом коде комментариев больше, чем самого кода. Хорошее оформление, полезная информация - годная статья Существование штатного механизма для написания сервисов в OpenOS тоже радует. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Belzebub 0 Опубликовано: 12 декабря, 2021 Как использовать rc config? Я хотел бы управлять конфигом прямо из самого сервиса, например создав функции: rc monitoring setscreen 1 -- устанавливает значение в конфиг rc monitoring getscreen -- принтит значение из конфига Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Belzebub 0 Опубликовано: 12 декабря, 2021 Хрень какая-то этот ваш rc, не пойму почему но таймеры ведут себя неадекватно - после первого запуска клоузуры таймер ремувиться ¯\_(ツ)_/¯ https://pastebin.com/guHTzhQG Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах