Event Machine — это потрясающая библиотека, которая обеспечивает основанный на событиях неблокирующий ввод-вывод в Ruby.
Вместо того чтобы сказать «записать в этот файл», а затем дождаться окончания этой процедуры, вы говорите «записать в этот файл и вызывать эту функцию, когда закончите», и ваша программа будет двигаться вперед. Преимущество заключается в том, что ожидания не требуются, а параллелизм становится намного проще.
Event Machine в основном работает с пулом потоков. Всякий раз, когда это необходимо, он использует некоторые потоки из пула и по завершении возвращает их обратно. Используя эти потоки, Event Machine упрощает разработку масштабируемых параллельных приложений.
В основном, в очень упрощенном (может быть, слишком упрощенном) выражении, это немного похоже на Node.js для Ruby.
Но с Node.js базовым языком является Javascript, который не имеет большого количества утилит по умолчанию для выполнения обычного старого (неосвященного) ввода-вывода, что означает, что его труднее запутать и написать неправильный тип код в неправильном месте.
Event Machine — это невероятно мощный инструмент для разработки мощных (например, в реальном времени) сервисов.
В любом месте, где нужно написать сервер для одновременной обработки множества соединений, записи в файловую систему и перехода на другие процессы или своевременного выпуска различных процессов, Event Machine отлично работает.
Итак, начнем с Event Machine.
Установка
Как мы все знаем, у Ruby есть замечательный RubyGems, который делает все невероятно простым. Просто подключите это к терминалу:
gem install eventmachine |
Если он задыхается от собственных расширений, убедитесь, что у вас установлены инструменты сборки (например, gcc), и прочитайте сообщение об ошибке, чтобы проверить, отсутствуют ли какие-либо необходимые библиотеки.
Эхо-сервер
Скромный эхо-сервер (сервер, который отправляет обратно тот же текст, который вы отправили ему), немного похож на «Hello, world!» Кода сокета — он ускоряет настройку.
Вот это с Event Machine:
require ‘eventmachine’ | |
class Echo < EM::Connection | |
def receive_data(data) | |
send_data(data) | |
end | |
end | |
EM.run do | |
EM.start_server(«0.0.0.0», 1337, Echo) | |
end |
Это выглядит пугающе, но, будучи разбитым, вполне усваивается. Мы создаем класс Echo, который является дочерним элементом EM :: Connection. Затем мы переопределяем метод receive_data
, который является методом обратного вызова . Методы обратного вызова вызываются, когда что-то происходит; в этом случае receive_data
вызывается, когда сервер получает данные.
receive_data
передается в данных в качестве аргумента, и затем мы используем метод send_data
(часть EM :: Connection) для отправки этих данных клиенту через то же EM :: Connection.
Затем мы используем метод EM.run
. Это запускает Event Machine, которая в основном является циклом, который запускает события. Требуется функциональный блок перед запуском. EM.start_server
запускает сервер EventMachine, используя класс Echo в качестве EM :: Connection.
Чтобы проверить это, мы можем подключиться к localhost через порт 1337:
telnet 127.0.0.1 1337 |
Напечатайте что-нибудь, нажмите Enter, и вы должны увидеть, что это эхо прямо на вас.
Теперь, что гораздо интереснее, откройте два терминала. Введите ту же команду, что и выше (например, подключитесь к эхо-серверу). Напечатайте что-нибудь в обоих соединениях, и вы должны получить ответ на обоих.
Сервер может обрабатывать несколько соединений одновременно!
Используя свой пул потоков, Event Machine волшебным образом сделала все одновременно.
Если вы написали многопоточный код, вы знаете, насколько это было сложно.
Существует много других методов, таких как receive_data
для переопределения (каждый из которых запускается при возникновении определенных событий), основными из которых являются:
-
connection_completed
— вызывается после завершения соединения -
post_init
—post_init
до установления соединения -
unbind
—unbind
после отключения клиента
Они не очень сложны в использовании; в этом отношении документация Event Machine просто великолепна. Однако может быть довольно сложно понять, как работает модель реактора (то есть основанная на событиях), и именно здесь происходит большинство ошибок.
предосторожность
Вы никогда не должны иметь блокирующий код в обратных вызовах событий.
Код блокировки состоит из методов, которые могут не возвращаться немедленно; например, открытие и чтение файла является хорошим примером.
Если вы это сделаете, вы убиваете цель системы, основанной на событиях (часто называемой схемой реактора), потому что ваша программа будет ждать, когда что-то произойдет. Это именно то, чего мы пытаемся избежать, используя схему реактора.
Обычно для таких задач Event Machine имеет свою собственную неблокирующую версию, которая подключается к среде выполнения Event Machine и запускает событие, когда оно выполнено.
Таймеры
Event Machine также обрабатывает таймеры. Вы можете назначить обратные вызовы, чтобы установить временные рамки.
Вот пример:
require ‘eventmachine’ | |
EM.run do | |
EM.add_periodic_timer(1) do | |
puts «time elapsed» | |
end | |
end |
На этот раз нам даже не нужен класс, потому что у нас нет связи. Код не требует пояснений: добавьте периодический таймер в цикл событий, и он запускает блок каждый раз, когда происходит событие. Блок просто выводит «прошедшее время» на экран.
Выполнение этого должно выдавать «истекшее время» каждую секунду.
Но что, если мы захотим остановить это через десять секунд? Вот код:
require ‘eventmachine’ | |
EM.run do | |
EM.add_timer(10) do | |
puts «STOP» | |
EM.stop_event_loop | |
end | |
EM.add_periodic_timer(1) do | |
puts «time elapsed» | |
end | |
end |
На этот раз мы используем новое средство под названием add_timer. Вместо того, чтобы периодически запускать события по истечении времени, он запускает событие только один раз, то есть по истечении первых 10 секунд.
Отсрочка
Скажем, вы хотите сделать что-то на фоне EM; то, что не влияет на клиентов сервера сразу.
Для этого вы можете использовать отсрочку. С отсрочкой вы можете отодвинуть задачу в фоновый режим, чтобы она не влияла на работу Event Machine.
Пример прояснит это:
require ‘eventmachine’ | |
EM.run do | |
EM.add_periodic_timer(1) do | |
puts «time elapsed» | |
end | |
EM.defer do | |
logs = IO.readlines(‘/var/log/kernel.log’) | |
end | |
end |
(Примечание: вам может потребоваться проверить путь /var/log/kernel.log в зависимости от вашей системы. Путь просто должен указывать на какой-то большой файл)
IO.readlines — это блокирующий вызов, т. Е. Нам нужно дождаться его окончания, прежде чем двигаться дальше. Но, используя EM.defer, мы можем сохранить наш периодический таймер таким, каким он должен быть.
Порожденные процессы
Порожденные процессы являются механизмами параллелизма, как и отложенные, но имеют некоторые особые качества.
Отсрочки запускаются, как только мы говорим «EM.defer block », с другой стороны, порожденные процессы зависают и ждут сообщения, а затем исполняются.
Пример:
require ‘eventmachine’ | |
EM.run do | |
spawned_process = EM.spawn do |did_what| | |
puts «I just » + did_what | |
end | |
EM.add_periodic_timer(1) do | |
spawned_process.notify «ate» | |
end | |
EM.add_periodic_timer(2) do | |
spawned_process.notify «slept» | |
end | |
end |
Итак, порожденный процесс выполняется, когда он получает уведомление с помощью сообщения.
Вывод
Подумай об этом на секунду. Мы работаем с полным механизмом потока здесь. Передача сообщений, обработка таймеров, использование фоновых потоков, обработка нескольких соединений, и это так просто. Например, пример порожденного процесса занял бы дни работы в C — Event Machine, я аплодирую вам.
Я надеюсь, что вам понравилось узнавать о Event Machine и что эти знания пригодятся вам.
Я хотел бы услышать ваши мысли о статье в разделе комментариев ниже.