Pull to refresh

Ruby и EventMachine

Reading time3 min
Views16K
Итак, EventMachine — быстрый и легкий фрэймворк для сетевого взаимодействия в Ruby. EventMachine используется событейно-ориентированный (асинхронный) механзим обработки сетевых соединений. (О различиях между синхронными и асинхронными моделями обработки сетевых соединений посвящено множество информации в сети).
Так как, в русскоязычном интернете очень скудная иноформация по этому замечательному gem'у выкладываю эту статью.

Установка достаточно стандарта для Ruby-программиста:
gem install eventmachine

Начнем с примера из документации (несколько измененного):

require 'rubygems'
require 'eventmachine'

class EchoServer < EventMachine::Connection
	def post_init
		puts "Соединение с сервером"
	end

	def receive_data data
		send_data ">>> #{data}"
		close_connection if data =~ /quit/i
	end

	def unbind
		puts "Соединение закрыто"
	end
end

EventMachine::run {
	EventMachine::start_server '', 8081, EchoServer
}


Теперь разберем по порядку (обратному — так будет удобнее):

EventMachine::run — инициализирует и запускает цикл обработки сообщений (EventReactor), возврат из этой функции происходит только при вызове метода stop_event_loop. Методу можно (и нужно) передать блок, который выполняется до запуска цикла обработки сообщения. Например, мы можем инициализировать здесь сервер, настроить таймеры, установить клиентское соединение и т.п. В данном же примере мы запускаем эхо-сервер, для этого служит метод (метод модуля EventMachine) EventMachine::start_server, его параметры это IP адрес и порт для прослушивания, в нашем примере IP пустой, чтобы можно было соединяться с любого хоста. Следующий и наверное самый важный параметр — это обработчик соединения — например, имя класса (подкласса EventMachine::Connection) или модуля (в этом случае модуль подмешивается к анонимному подклассу EventMachine::Connection). В этом примере это класс EchoServer.
На каждое соединение с сервером инициируется свой объект класса EchoServer!!!
В цикле обработке сообщений (EventReactor) инициируются сообщения — в данном контексте — это вызовы методов объекта класса EchoServer. Основными сообщениями являются:
  • post_init — вызывается после установки соединения
  • unbind — вызывается при обрыве соединения
  • receive_data — вызывается при получении сообщения из соединения

В приведенном примере, после установки соединения с клиентом выводиться сообщение — «Соедиение с сервером», при обрыве «Соединение закрыто». При получение сообщения оно отправляется назад используя метод send_data (отправить сообщение), в случае если получено сообщение quit — соединение закрывается.
Более подробно, о методах класса смотрите в документации.

С клиентом все почти по аналогии — примеры можно найти в интернете в том числе и на русском языке.
Вообще проще всего можно протестировать используя telnet:
telnet localhost 11777

Мы же рассмотрим более интересные темы.
Я уже встречал на хабре чат сделанный с использованием EventMachine, однако там не использовались все его вкусности. Сейчас мы напишем простенький чат (не буду врать, но думаю что 1000 коннектов он точно выдержит, если чуть-чуть поправить код, в качестве подсказки — метод EventMachine.defer).

Рассмотрим каналы EventMachine::Channel. Этот механизм совсем не нов и используется в различных системах, более того это целый паттерн. Механизм следующий — есть канал (ну скажем длинный коридор этажа офисного здания), и есть подписчики (те кто открыл двери, чтобы слышать все что происходит в коридоре). Любой, даже не подписчик (тот кто не открывал дверь) может послать сообщение в канал (крикнуть в коридор все что угодно) и все подписчики его получат (услышат). От канала можно отписаться (закрыть дверь).

Итак, у канала есть три метода: subscribe (подписаться), push (послать сообщение), unsubscribe (отписаться). Ниже представлена реализация чата (с одним каналом — комнатой)

require 'rubygems'
require 'eventmachine'
require 'socket'

class User < EventMachine::Connection
	@@room = EventMachine::Channel.new

	attr_reader :port, :ip, :sid

	def receive_data data
		@@room.push("#{ip}:#{port} >>> #{data}")
		close_connection if data =~ /quit/i
	end

	def unbind
		str = "Ушел #{ip}:#{port}\n"
		@@room.push(str)
		puts str

		@@room.unsubscribe(sid)
	end

	def post_init
		@port, @ip = Socket.unpack_sockaddr_in(get_peername)
		str = "Пришел поговорить #{ip}:#{port}\n"
		@sid = @@room.subscribe { |msg| send_data msg }
		@@room.push(str)
		puts str
	end
end

EventMachine::run {
	EventMachine::start_server '', 11777, User
}


Вообще, программирование с использование EventMachine несколько сложнее обычного. Связано это с асинхронностью, использование большого количества блоков, процедур и прочего. В приведенных выше примерах этого почти не видно, но если вы захотите использоваться EventMachine вам придется с этим столкнуться.
Tags:
Hubs:
+25
Comments10

Articles