defmodule Message do defstruct sender_name: "", sender_pid: nil, type: nil, args: [] end defmodule Info do defstruct name: "", others: %{} end defmodule Server do defstruct pid: nil, client_pid: nil, handler: nil, info: %Info{} def run(name, handler) do state = %Server { pid: self(), handler: handler, info: %Info{name: name} } server(state) end defp server(state) do state = receive do msg -> case msg.type do :start -> start(state, msg) :ping -> pong(state, msg) _ -> state.handler.(state, msg) end end server(state) end defp start(state, msg) do [others|_] = msg.args state = put_in(state.info.others, others) client_pid = spawn(Client, :run, [self(), state.info]) state = put_in(state.client_pid, client_pid) state end defp pong(state, msg) do IO.puts "<#{state.info.name}> #{msg.sender_name}: PONG!" state end end defmodule Client do defstruct pid: nil, server_pid: nil, info: %Info{} def run(server_pid, info) do me = %Client{ pid: self(), server_pid: server_pid, info: info, } Enum.each info.others, fn other -> ping(me, other) end end defp ping(me, {recp_name, recp_pid}) do msg = %Message{ sender_name: me.info.name, sender_pid: me.server_pid, type: :ping, args: [] } IO.puts "<#{me.info.name}> #{recp_name}: PING!" send(recp_pid, msg) end end defmodule Person do def run(name, handler) do spawn(Server, :run, [name, handler]) end end defmodule Ref do def run() do get_names(4) |> get_people() |> start_people() end defp get_names(n) do Enum.map (1..n), &(String.to_atom inspect &1) end defp get_people(names) do default_handler = fn state, msg -> IO.puts "unhandled #{inspect msg.type}" state end Keyword.new names, fn name -> {name, Person.run(name, default_handler)} end end defp start_people(people) do Enum.each people, fn person = {_name, server} -> others = Enum.filter people, fn other -> other != person end msg = %Message{ sender_name: "ref", sender_pid: self(), type: :start, args: [others]} send(server, msg) end people end end defmodule Main do Ref.run() end