Elixir – TCP Server Example

It’s the Elixir version, following after the Ruby(EventMachine) and Go ones. Basically it ended up to similar as Go, due to the similarity of go’s channel and Elixir’s spawn/receive statements. There seems some differences in error handling between the two, but it’s not covered here.

Notes

  • Good
    • Elixir’s pattern matching provides polymorphic mechanism. After getting used to this functional concept (no if statement and no loops), it might gets interesting to write codes.
    • Calling Erlang’s library is not so difficult. It provides some sense of comfort when facing issues (there would be some reasonable solutions).
  • Bad
    • Error messages are sometimes cryptic. When child process dies with error, error is presented in weired form, though it has information of locations of error.

Server

$ elixir -r lib/tcpserver.ex -e "Tcpserver.listen(3000)"
2013/7/28 15:11:57 Completed in 5 second(s) with param = A
2013/7/28 15:11:59 Completed in 5 second(s) with param = B
2013/7/28 15:12:0 Completed in 5 second(s) with param = C
2013/7/28 15:12:5 Completed in 1 second(s) with param = E
2013/7/28 15:12:8 Completed in 5 second(s) with param = D
2013/7/28 15:12:10 Invalid command is specified

Client

$ telnet localhost 3000
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
LARGE A
LARGE B
LARGE C
Completed in 5 second(s) with param = A
Completed in 5 second(s) with param = B
Completed in 5 second(s) with param = C
LARGE D
SMALL E
Completed in 1 second(s) with param = E
Completed in 5 second(s) with param = D
xxx
Invalid command is specified

SouceCode (tcpserver.ex)

defrecord LargeJob, name: "" do
  def run(record) do
    :timer.sleep(5000)
    "Completed in 5 second(s) with param = #{record.name}"
  end
end

defrecord SmallJob, name: "" do
  def run(record) do
    :timer.sleep(1000)
    "Completed in 1 second(s) with param = #{record.name}"
  end
end

defrecord InvalidJob, name: "" do
  def run(_record) do
    "Invalid command is specified"
  end
end

defmodule Job do
  def handle_request(sender, command) do
    command = String.replace(command, "\r\n", "")
    job = parse(String.split(command, " "))
    sender <- {:ok, job.run()}
  end

  defp parse(["LARGE", name]) do
    LargeJob.new name: name
  end

  defp parse(["SMALL", name]) do
    SmallJob.new name: name
  end

  defp parse([_]) do
    InvalidJob.new
  end
end

defmodule Tcpserver do
  def listen(port) do
    tcp_options = [:list, {:packet, 0}, {:active, false}, {:reuseaddr, true}]
    {:ok, listen_socket} = :gen_tcp.listen(port, tcp_options)
    do_listen(listen_socket)
  end

  defp do_listen(listen_socket) do
    {:ok, socket} = :gen_tcp.accept(listen_socket)
    spawn(fn() -> do_server(socket) end)
    do_listen(listen_socket)
  end

  defp do_server(socket) do
    case :gen_tcp.recv(socket, 0) do
      {:ok, data} ->
        responder = spawn(fn() -> do_respond(socket) end)
        spawn(Job, :handle_request, [responder, list_to_binary(data)])
        do_server(socket)

      {:error, :closed} -> :ok
    end
  end

  defp do_respond(socket) do
    receive do
      {:ok, response} ->
        :gen_tcp.send(socket, "#{response}\n")
        Logger.log(response)
    end
  end
end

defmodule Logger do
  def log(message) do
    {{year, month, day}, {hour, min, sec}} = :erlang.localtime
    IO.puts "#{year}/#{month}/#{day} #{hour}:#{min}:#{sec} #{message}"
  end
end
Advertisements

Posted on July 28, 2013, in Elixir, Software Design. Bookmark the permalink. Leave a comment.

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: