Перейти к публикации
Форум - ComputerCraft
LeshaInc

OpenOS RC. Что за зверь такой?

Рекомендованные сообщения

Если вы когда нибудь сталкивались с созданием серверов под OpenOS, вам знакома проблема блокирования всего компьютера. Его нельзя использовать, пока сервер не выключится. Некоторые придумывали свои велосипеды, которые запускали event.listen или event.ignore. Но не то это!
 
Для таких вещей существует rc, в OpenOS. Сейчас я расскажу вам, что это за зверь, где он живет и зачем он нам.


Немного теории B-)
Итак, все скрипты для 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

Ну вот и все, программа сделана, диванные аналитики ликуют, инженеры недоумевают. :dirol:

 

Полный код:

 

-- нам понадобятся библиотека 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! :crazy_pilot:

 

Симулируем Васю Пупкина...

 

 

44e93ee134de491ab6b7519862f020ff.png

 

 

 

А вот уже на другом компьютере отправлено сообщение на работающий сервер.

 

 

7822297da5f14a0baf29a59a0228377e.png

 

 

 

Счетчик тоже работает.

 

 

983e4a7a0519474593e344c96969d00a.png

 

 

 

Установим значение в конфиге.

 

 

0a120b12376d4428b7cd6753bb401928.png

 

 

 

Проверим...

 

 

491e0b36172d47d3b33511747b0c5a3a.png

 

 

 

Все работает, инженеры ликуют!  :)

 


 

Такую систему очень удобно использовать для всяческих серверов. Сервер может спокойно работать в фоне, а в главном потоке спокойно можно запускать консоль сервера, интерпретатор луа,  rm -rf /  ...

 

Enjoy!

Изменено пользователем LeshaInc
  • Like 7

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

event.listen forever  ;)

 

Это точно.

 

Весь код в спойлеры надо добавить.

 

Со спойлерами пропадает весь запах "крутого туториала". :) А еще пропадает юзабилити, надо тыкать на эти кнопуськи "Открыть", не удобно ИМХО.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Весь код в спойлеры надо добавить.

 

Имхо спойлеры эти выглядят фигово в статьях.

Плюс, это туториал. Код тут такой же элемент статьи как и текст. Читаешь текст, читаешь код. Получаешь комплексное впечатление.

А необходимость постоянно тыкать на спойлеры будет раздражать.

Ну и наконец, фрагменты кода просто визуально разбавляют унылую стену текста.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Имхо спойлеры эти выглядят фигово в статьях.

Плюс, это туториал. Код тут такой же элемент статьи как и текст. Читаешь текст, читаешь код. Получаешь комплексное впечатление.

А необходимость постоянно тыкать на спойлеры будет раздражать.

Ну и наконец, фрагменты кода просто визуально разбавляют унылую стену текста.

 

 

Это точно.

 

 

Со спойлерами пропадает весь запах "крутого туториала". :) А еще пропадает юзабилити, надо тыкать на эти кнопуськи "Открыть", не удобно ИМХО.

:facepalm:

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Весь код в спойлеры надо добавить.

В этом коде комментариев больше, чем самого кода. Хорошее оформление, полезная информация - годная статья

 

Существование штатного механизма для написания сервисов в OpenOS тоже радует.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×