sockets 和 channels 是Phoenix中用来实现实时效果的两大工具。
Sockets
socket是用来连接客户端与服务器的,它使用endpoint来声明:
defmodule GenPoker.Endpoint do
use Phoenix.Endpoint, otp_app: :gen_poker
socket "/socket", GenPoker.PlayerSocket
end
Channels
客户端只有加入了channel之后才能发送消息。
defmodule GenPoker.PlayerSocket do
use Phoenix.Socket
channel "tables:*", GenPoker.TableChannel
end
创建socket
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中可以这样写:
channel.push("message", arguments)
.receive("ok", (msg) => console.log("Got OK!"))
.receive("error", (msg) => console.log("Oops!"))
发送初始的state
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_info
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
拦截消息
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
来源:oschina
链接:https://my.oschina.net/u/2864245/blog/754811