EventMachine – UDP Server Example

Just made some trials on “EventMachine” as I got interested in the job-handling in the server/client model, after the investigation for the previous post (Code Smells @ YouTube).

Regarding the EventMachine, the following is the excerpt from the README.

http://eventmachine.rubyforge.org/

What is EventMachine

EventMachine is an event-driven I/O and lightweight concurrency library for Ruby. It provides event-driven I/O using the Reactor pattern, much like JBoss Netty, Apache MINA, Python’s Twisted, Node.js, libevent and libev.

PeepCode has nice screencasts for the EventMachine, and I coded some examples based on that.

The previous post was about a simple UDP server to handle user-specified jobs. I wrote similar feature using EventMachine as follows. The server accepts jobs like “SMALL” or “LARGE” which takes several seconds to complete, and then responds with a message. In the following example, standard “nc” command is used for the client part.

With EventMachine, asynchronous job handling can be simply implemented with the event-driven model. It also just works concurrently. It’s nice.

Server

% ruby server.rb
2013-07-14T00:40:00+09:00 : Received LARGE A
2013-07-14T00:40:02+09:00 : Received LARGE B
2013-07-14T00:40:03+09:00 : Received LARGE C
2013-07-14T00:40:05+09:00 : Completed in 5 second with param = A
2013-07-14T00:40:07+09:00 : Completed in 5 second with param = B
2013-07-14T00:40:08+09:00 : Completed in 5 second with param = C
2013-07-14T00:40:35+09:00 : Received LARGE D
2013-07-14T00:40:36+09:00 : Received SMALL E
2013-07-14T00:40:37+09:00 : Completed in 1 second with param = E
2013-07-14T00:40:40+09:00 : Completed in 5 second with param = D
2013-07-14T00:40:54+09:00 : Received XXX
2013-07-14T00:40:54+09:00 : Invalid command is specified

Client

% nc -u 0.0.0.0 9000
LARGE A
LARGE B
LARGE C
Completed in 5 second with param = A
Completed in 5 second with param = B
Completed in 5 second with param = C
LARGE D
SMALL E
Completed in 1 second with param = E
Completed in 5 second with param = D
XXX
Invalid command is specified

SourceCode

require 'rubygems'
require 'eventmachine'
require 'date'

class Job
  def initialize(param = "")
    @param = param
  end

  def run(defer)
    throw "This method needs to be implemented in subclasses"
  end

private
  def process(defer, time, message)
    EM.defer do
      sleep(time)
      defer.succeed(message)
    end
  end
end

class SmallJob < Job
  def run(defer)
    process(defer, 1, "Completed in 1 second with param = #{@param}")
  end
end

class LargeJob < Job
  def run(defer)
    process(defer, 5, "Completed in 5 second with param = #{@param}")
  end
end

class InvalidJob < Job
  def run(defer)
    process(defer, 0, "Invalid command is specified")
  end
end

class RequestHandler
  JOBS = {
    "SMALL" => SmallJob,
    "LARGE" => LargeJob
  }
  JOBS.default = InvalidJob

  def self.parse(command)
    type, param = command.split
    JOBS[type].new(param)
  end
end

class UDPHandler < EM::Connection
  def receive_data(command)
    command.chomp!
    log("Received #{command}")
    RequestHandler.parse(command).run(callback)
  end

private
  def callback
    EM::DefaultDeferrable.new.callback do |response|
      send_data(response + "\n")
      log(response)
    end
  end

  def log(message)
    puts "#{DateTime.now.to_s} : #{message}"
  end
end

EM.run do
  EM.open_datagram_socket('0.0.0.0', 9000, UDPHandler)
end
Advertisements

Posted on July 14, 2013, in Ruby, Web. Bookmark the permalink. 2 Comments.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: