sockets 和 channels 是Phoenix中用來實現實時效果的兩大工具。git
socket是用來鏈接客戶端與服務器的,它使用endpoint來聲明:github
defmodule GenPoker.Endpoint do use Phoenix.Endpoint, otp_app: :gen_poker socket "/socket", GenPoker.PlayerSocket end
客戶端只有加入了channel以後才能發送消息。web
defmodule GenPoker.PlayerSocket do use Phoenix.Socket channel "tables:*", GenPoker.TableChannel end
defmodule GenPoker.PlayerSocket do use Phoenix.Socket transport :websocket, Phoenix.Transports.WebSocket def connect(%{"playerId" => player_id}, socket) do {:ok, assign(socket, :player_id, player_id)} end def id(socket) do "players_socket:#{socket.assigns.player_id}" end end
defmodule Poker.Table do use GenServer def start_link(table_name, sup, storage, num_seats) do GenServer.start_link( __MODULE__, [table_name, sup, storage, num_seats], name: via_tuple(table_name) ) end defp via_tuple(table) do {:via, :gproc, {:n, :l, {:table, table}}} end def whereis(table) do :gproc.whereis_name({:n, :l, {:table, table}}) end end
咱們使用了gproc庫來註冊進程,這樣就能夠使用一個term而不單單是atom做爲名字。讓咱們來定義Channel:服務器
module GenPoker.TableChannel do use GenPoker.Web, :channel alias Poker.Table def join("tables:" <> table, _payload, socket) do {:ok, assign(socket, :table, table)} end def handle_in(command, payload, socket) when command in ~w(sit leave buy_in cash_out deal) do table = Table.whereis(socket.assigns.table) arguments = [table, socket.assigns.player_id] ++ payload result = apply(Table, String.to_atom(command), arguments) if result == :ok do broadcast! socket, "update", Table.get_state(table) end {:reply, result, socket} end end
對於客戶端的join請求,咱們有不一樣的回覆。在JavaScript中能夠這樣寫:websocket
channel.push("message", arguments) .receive("ok", (msg) => console.log("Got OK!")) .receive("error", (msg) => console.log("Oops!"))
def join("tables:" <> table, _payload, socket) do state = table |> Table.whereis |> Table.get_state push socket, "update", state {:ok, assign(socket, :table, table)} end
使用handle_infoapp
def join("tables:" <> table, _payload, socket) do send self, :after_join {:ok, assign(socket, :table, table)} end def handle_info(:after_join, socket) do state = socket.assigns.table |> Table.whereis |> Table.get_state push socket, "update", state {:noreply, socket} end def handle_info(_, socket) do {:noreply, socket} end
攔截消息socket
def handle_out("update", state, socket) do push socket, "update", hide_other_hands(state, socket) {:noreply, socket} end defp hide_other_hands(state, socket) do player_id = socket.assigns.player_id hide_hand_if_current_player = fn %{id: ^player_id} = player -> player player -> Map.delete(player, :hand) end update_in(state.players, fn players -> Enum.map(players, hide_hand_if_current_player) end) end
完整代碼ide