Skip to content
MarkBennett edited this page Dec 28, 2010 · 34 revisions

Client Example


#!/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::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)
}

Server Example


#!/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'
  }

Trying Things Out

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...

Keyboard Input Example

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.

Safe run in any circumstances

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.

Writing out to clients before Exit

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"
}

Ports

Start a server on an unassigned port

  require ‘socket’

  EM.run {
    srvr = EM.start_server “0.0.0.0”, 0
    p Socket.unpack_sockaddr_in( EM.get_sockname( srvr ))
  }

Determine clients port

  port, *ip_parts = get_peername[2,6].unpack "nC4"
  ip = ip_parts.join('.')

or

  require 'socket'
  port, ip = Socket.unpack_sockaddr_in(get_peername)

EM over a Serial Port

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

Using EventMachine with GTK+

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.