为什么80%的码农都做不了架构师?>>>
sockets 和 channels 是Phoenix中用来实现实时效果的两大工具。
Sockets
socket是用来连接客户端与服务器的,它使用endpoint来声明:
defmodule GenPoker.Endpoint douse Phoenix.Endpoint, otp_app: :gen_pokersocket "/socket", GenPoker.PlayerSocket
end
Channels
客户端只有加入了channel之后才能发送消息。
defmodule GenPoker.PlayerSocket douse Phoenix.Socketchannel "tables:*", GenPoker.TableChannel
end
创建socket
defmodule GenPoker.PlayerSocket douse Phoenix.Sockettransport :websocket, Phoenix.Transports.WebSocketdef connect(%{"playerId" => player_id}, socket) do{:ok, assign(socket, :player_id, player_id)}enddef id(socket) do "players_socket:#{socket.assigns.player_id}"end
end
注册进程
defmodule Poker.Table douse GenServerdef start_link(table_name, sup, storage, num_seats) doGenServer.start_link(__MODULE__, [table_name, sup, storage, num_seats], name: via_tuple(table_name))enddefp via_tuple(table) do {:via, :gproc, {:n, :l, {:table, table}}}enddef whereis(table) do:gproc.whereis_name({:n, :l, {:table, table}})end
end
我们使用了gproc库来注册进程,这样就可以使用一个term而不仅仅是atom作为名字。让我们来定义Channel:
module GenPoker.TableChannel douse GenPoker.Web, :channelalias Poker.Tabledef join("tables:" <> table, _payload, socket) do{:ok, assign(socket, :table, table)}enddef handle_in(command, payload, socket) when command in ~w(sit leave buy_in cash_out deal) dotable &#61; Table.whereis(socket.assigns.table)arguments &#61; [table, socket.assigns.player_id] &#43;&#43; payloadresult &#61; apply(Table, String.to_atom(command), arguments)if result &#61;&#61; :ok dobroadcast! socket, "update", Table.get_state(table)end{:reply, result, socket}end
end
对于客户端的join请求&#xff0c;我们有不同的回复。在Javascript中可以这样写&#xff1a;
channel.push("message", arguments).receive("ok", (msg) &#61;> console.log("Got OK!")).receive("error", (msg) &#61;> console.log("Oops!"))
发送初始的state
def join("tables:" <> table, _payload, socket) dostate &#61; table |> Table.whereis |> Table.get_statepush socket, "update", state{:ok, assign(socket, :table, table)}
end
使用handle_info
def join("tables:" <> table, _payload, socket) dosend self, :after_join{:ok, assign(socket, :table, table)}
enddef handle_info(:after_join, socket) dostate &#61; socket.assigns.table |> Table.whereis |> Table.get_statepush socket, "update", state{:noreply, socket}
enddef handle_info(_, socket) do{:noreply, socket}
end
拦截消息
def handle_out("update", state, socket) dopush socket, "update", hide_other_hands(state, socket){:noreply, socket}
enddefp hide_other_hands(state, socket) doplayer_id &#61; socket.assigns.player_idhide_hand_if_current_player &#61; fn%{id: ^player_id} &#61; player -> playerplayer -> Map.delete(player, :hand)endupdate_in(state.players, fn players ->Enum.map(players, hide_hand_if_current_player)end)
end
完整代码