Fork me on GitHub

Communicating from Ruby to Erlang

16 November 2008

I’ve been working pretty hard on PoolParty lately. PoolParty is a combination of ruby and erlang and gives full access to the cloud using a console.

  pool console

I’m going to show you how I implemented the console, which is a container for irb and lightweight socket communication to the messenger written in erlang.

I’ll assume that you know enough about erlang to get through the code as beginning erlang is out of scope for this tutorial.

First, let’s look at the erlang messenger

-module (server).
-export (start/0).

start() ->
    {ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}, {active, false}]),
    active(LSock).

accept(Socket) ->
  {ok, Sock} = gen_tcp:accept(Socket),
  spawn(fun() -> loop(do_recv(Sock)) end),
  accept(Socket).

do_recv(Sock) ->
    case gen_tcp:recv(Sock, 0) of
        {ok, B} ->
          [Meth|Args] = string:tokens(erlang:binary_to_list(Data), " "),
          case Meth of
            {time} ->
              {Date={Year,Month,Day},Time={Hour,Minutes,Seconds}}
                = erlang:localtime(),
              IntString =
                [ erlang:integer_to_list(A) || A <- [Hour, Minutes, Seconds] ],
              ResponseString = lists:append(IntString),
              gen_tcp:send(Sock, ResponseString),
              do_recv(Sock);
            _ ->
              gen_tcp:send(Sock, B),
              do_recv(Sock);
          end;
        {error, closed} ->
          {ok, list_to_binary(Bs)}
    end.

Now we have a spawning server that spawns off responses to respond to the requests that comes into the server, handles them and sends back their responses to the caller (using gen_tcp). In our case, we’ll be calling the contrived time method that will send us back the hour minutes and seconds

We can start the server like this

auser ~ $ erl
Erlang (BEAM) emulator version 5.6.2 [source] [async-threads:0] [kernel-poll:false]

Eshell V5.6.2  (abort with ^G)
1> c(server.erl).
2> server:start(7050).

Now that we have the server running, let’s get our ruby side talking. Our gen_tcp-based server above starts a listener at the port 5678, sounds like something we can hook up using ruby’s TCPSocket class. Which is exactly what we will do:

require "socket"
  module Messenger
    def initialize(host="localhost", port=5678)
      @host = host;@port = port
    end
    def with_socket(&block)
      begin
        socket = TCPSocket.open(@host, @port)
        out = yield(socket)
        socket.close
        out
      rescue Exception => e
      end
    end
    def call!(msg="time")
      with_socket do |sock|
        sock.send(msg, 0)
        @str = sock.recv(2000)
      end
      @str
    end
    def cast!(msg="list")
      with_socket do |sock|
        sock.send(msg, 0)
      end
    end
  end

So we can call call! on any class that includes Messenger and it will send the data using a socket we open with the with_socket method. Upon sending the message, we also immediately open a socket to listen for the response. If we just want to send a message to the erlang messenger, then we can just call cast! with the message we want to send

class Test
    include Messenger
  end

@t = Test.new
@t.call!("time")

That about wraps up this edition of inside PoolParty, check out Labs at CitrusByte for more information about PoolParty.

Have fun! -Ari