Help us understand the problem. What is going on with this article?

ElixirのSocketモジュールを使ってBroadcastを実装してみた

More than 1 year has passed since last update.

ElixirのSocketモジュールを使ってWebRTCのシグナリングサーバーを
実装してみようかなと思ったのですが、
そもそもBroadcast部分も自分で実装しないといけない雰囲気だったので実装してみました

使用モジュールはelixir-socket のみです

lib/elixir_socket_testing/server.ex
defmodule ElixirSocketTesting.Server do

  alias ElixirSocketTesting.ClientAgent

  def start(port \\ 9000) do
    # クライアント保持用のAgent
    ClientAgent.start_link

    # WebSocketサーバーリッスン
    server = Socket.Web.listen! port

    loop(server)
  end

  def loop(server) do

    # クライアントからの接続を待ち受ける
    client = server |> Socket.Web.accept!
    client |> Socket.Web.accept!

    # クライアントをAgentに追加
    ClientAgent.add(client)

    # 各クライアントからのメッセージハンドリングは別プロセスで行う
    Task.async(fn -> recv(client) end)

    loop(server)
  end 

  def recv(client) do
    # クライアントからのメッセージをハンドリング
    case client |> Socket.Web.recv! do
      {:text, message} -> 
        broadcast({:text, message})
        recv(client)
      {:close, _, _} -> ClientAgent.remove(client)
      other ->  IO.inspect other
    end
  end

  def broadcast(packet) do
    # Agentに保持している全てのクライアントにメッセージを送信
    ClientAgent.all
    |> Enum.each(fn client -> Socket.Web.send(client, packet) end)
  end

end

以下の3点をやってるだけですね
- クライントのリストを保持しておくAgentを用意しておきクライアントから接続があったら
Agentにつっこんでおきます。
- クライアントからメッセージが来たら保持しているクライアント全てにメッセージを送信する
- クライアントからの接続が閉じたらリストから削除する

クライアントの保持用のAgentは以下のような感じになります

lib/elixir_socket_testing/client_agent.ex
defmodule ElixirSocketTesting.ClientAgent do

  def start_link do
    Agent.start_link(fn -> [] end, name: __MODULE__)
  end

  def add(client) do
    Agent.update(__MODULE__, fn list -> [client | list]  end)
  end

  def remove(client) do
    Agent.update(__MODULE__, fn list -> List.delete(list, client) end)
  end

  def all do
    Agent.get(__MODULE__, fn list -> list end)
  end
end

iexで動かしてみます

サーバー
$ iex -S mix
iex> ElixirSocketTesting.Server.start

別窓を3つほど立ち上げてそれぞれ接続して、
クライアント1, 2 でrecvしたあとにクライアント3でメッセージを送ってみました。

demo.gif

自分にもメッセージを送ってしまっていますが、他のクライアントにもメッセージが
送られているのがわかります。

簡単な実装ではありましたが思いがけずAgentとTaskの練習になった気がしています
Taskで非同期にしている部分などはSupervisorにぶら下げるとよりElixirでの
アプリケーションっぽくなるのかなぁ。。。と思ったり。

今回のコードはgithubにもあげていいます
elixir_socket_testing

tamanugi
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away