-
Notifications
You must be signed in to change notification settings - Fork 0
Code Snippets
#!/usr/bin/env ruby
#
# client_1
require 'rubygems'
require 'eventmachine'
class Echo < EventMachine::Connection
def post_init
send_data 'Hello'
end
def receive_data(data)
p data
end
end
EventMachine.run {
EventMachine.connect '127.0.0.1', 8081, Echo
}
A more complicated example:
#!/usr/bin/env ruby
#
# client_2
require 'rubygems'
require 'eventmachine'
class Echo < EventMachine::Connection
def initialize(*args)
super
# stuff here...
end
def receive_data(data)
p data
send_data data
close_connection_after_writing
end
def unbind
p ' connection totally closed'
end
end
EventMachine.run {
EventMachine.connect '127.0.0.1', 8081, Echo
}
An even more complicated example, with keyboard input and queuing:
#!/usr/bin/env ruby
#
# client_3
require 'rubygems'
require 'eventmachine'
class Echo < EM::Connection
attr_reader :queue
def initialize(q)
@queue = q
cb = Proc.new do |msg|
send_data(msg)
q.pop &cb
end
q.pop &cb
end
def post_init
send_data('Hello')
end
def receive_data(data)
p data
end
end
class KeyboardHandler < EM::Connection
include EM::Protocols::LineText2
attr_reader :queue
def initialize(q)
@queue = q
end
def receive_line(data)
@queue.push(data)
end
end
EM.run {
q = EM::Queue.new
EM.connect('127.0.0.1', 8081, Echo, q)
EM.open_keyboard(KeyboardHandler, q)
}
#!/usr/bin/env ruby
#
# server_1
require 'rubygems'
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
send_data ">>> you sent: #{data}"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 8081, EchoServer
puts 'running echo server on 8081'
}
To try things out, you’ll need two windows. In window 1, start up server_1:
% server_1
running echo server on 8081
Then, in window 2, telnet to server_1 and type something…
% telnet localhost 8081
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying fe80::1...
telnet: connect to address fe80::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
hiho
>>> you sent: hiho
^]
You can also try out the client and server together. In window 2, start up client_1:
% client_1
">>> you sent: Hello"
^C...
Or, try client_3:
% client_3
">>> you sent: Hello"
foo!
">>> you sent: foo!"
^C...
EventMachine can respond to keyboard events. This gives your event-driven programs the ability to respond to input from local users.
Programming EM to handle keyboard input in Ruby is simplicity itself. Just use EventMachine#open_keyboard, and supply the name of a Ruby module or class that will receive the input:
require 'rubygems'
require 'eventmachine'
module MyKeyboardHandler
def receive_data keystrokes
puts "I received the following data from the keyboard: #{keystrokes}"
end
end
EM.run {
EM.open_keyboard(MyKeyboardHandler)
}
If you want EM to send line-buffered keyboard input to your program, just include the LineText?2 protocol module in your handler class or module:
require 'rubygems'
require 'eventmachine'
module MyKeyboardHandler
include EM::Protocols::LineText2
def receive_line data
puts "I received the following line from the keyboard: #{data}"
end
end
EM.run {
EM.open_keyboard(MyKeyboardHandler)
}
As we said, simplicity itself. You can call EventMachine#open_keyboard at any time while the EM reactor loop is running. In other words, the method invocation may appear anywhere in an EventMachine#run block, or in any code invoked in the #run block.
As of EM 0.12 it is safe to call the #run
method multiple times. A good pattern for managable code is to push the call to #run
to the top of your application stack, and do your threaded work using EM::defer
where possible.
Let connections finish their work before stopping EM for good.
class Server
attr_accessor :connections
def initialize
@connections = []
end
def start
@signature = EventMachine.start_server('0.0.0.0', 3000, Connection) do |con|
con.server = self
end
end
def stop
EventMachine.stop_server(@signature)
unless wait_for_connections_and_stop
# Still some connections running, schedule a check later
EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
end
end
def wait_for_connections_and_stop
if @connections.empty?
EventMachine.stop
true
else
puts "Waiting for #{@connections.size} connection(s) to finish ..."
false
end
end
end
class Connection < EventMachine::Connection
attr_accessor :server
def unbind
server.connections.delete(self)
end
end
EventMachine::run {
s = Server.new
s.start
puts "New server listening"
}
require 'socket'
EM.run {
srvr = EM.start_server "0.0.0.0", 0
p Socket.unpack_sockaddr_in( EM.get_sockname( srvr ))
}
port, *ip_parts = get_peername[2,6].unpack "nC4"
ip = ip_parts.join('.')
or
require 'socket'
port, ip = Socket.unpack_sockaddr_in(get_peername)
It is actually possible to use Guilliame Perionnet’s ruby-serialport library with (the pure Ruby) EventMachine. The following code works with EventMachine 0.12.2 and ruby-serialport 0.7.0:
$eventmachine_library = :pure_ruby # need to force pure ruby
require 'eventmachine'
gem_original_require 'serialport'
require 'smsrelay/gsmpdu'
module EventMachine
class EvmaSerialPort < StreamObject
def self.open(dev, baud, databits, stopbits, parity)
io = SerialPort.new(dev, baud, databits, stopbits, parity)
return(EvmaSerialPort.new(io))
end
def initialize(io)
super
end
##
# Monkeypatched version of EventMachine::StreamObject#eventable_read so
# that EOFErrors from the SerialPort object (which the ruby-serialport
# library uses to signal the fact that there is no more data available
# for reading) do not cause the connection to unbind.
def eventable_read
@last_activity = Reactor.instance.current_loop_time
begin
if io.respond_to?(:read_nonblock)
10.times {
data = io.read_nonblock(4096)
EventMachine::event_callback uuid, ConnectionData, data
}
else
data = io.sysread(4096)
EventMachine::event_callback uuid, ConnectionData, data
end
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, EOFError
# no-op
rescue Errno::ECONNRESET, Errno::ECONNREFUSED
@close_scheduled = true
EventMachine::event_callback uuid, ConnectionUnbound, nil
end
end
end
class << self
def connect_serial(dev, baud, databits, stopbits, parity)
EvmaSerialPort.open(dev, baud, databits, stopbits, parity).uuid
end
end
def EventMachine::open_serial(dev, baud, databits, stopbits, parity,
handler=nil)
klass = if (handler and handler.is_a?(Class))
handler
else
Class.new( Connection ) {handler and include handler}
end
s = connect_serial(dev, baud, databits, stopbits, parity)
c = klass.new s
@conns[s] = c
block_given? and yield c
c
end
class Connection
# This seems to be necessary with EventMachine 0.12.x
def associate_callback_target(sig)
return(nil)
end
end
end
GTK+ applications generally make use of a blocking event loop ( gtk_main / Gtk::main ) to do their work, which interacts poorly with EventMachine. The way around it seems to be to explicitly “give it a tick”, like so:
require 'gtk2'
require 'eventmachine'
class MyApp
def initialize
EM::connect ...
end
end
EM::run do
client = MyApp.new
give_tick = proc { Gtk::main_iteration; EM.next_tick(give_tick); }
give_tick.call
end
This avoids the blocking event loop (which stops EM from working) by running one iteration of its main loop every tick of EventMachine. This ensures GTK events are processed in a timely fashion without blocking EM from doing its work.
There might be some way to use gtk_idle_add() to run EM inside GTK’s event loop – but I’ve not had any success with that.
require 'eventmachine'
require 'Qt4'
app = Qt::Application.new(ARGV)
hello_button = Qt::PushButton.new("Hello EventMachine")
hello_button.resize(100,20)
hello_button.show
EventMachine.run do
EM.add_periodic_timer(0.01) do
app.process_events
end
end