先在 Endpoint 开 socket 入口:
socket "/socket", CityWeb.UserSocket,
websocket: true,
longpoll: false定义用户 socket:
defmodule CityWeb.UserSocket do
use Phoenix.Socket
channel "district:*", CityWeb.DistrictChannel
def connect(%{"token" => token}, socket, _connect_info) do
case CityWeb.SocketAuth.verify(token) do
{:ok, user} ->
{:ok, assign(socket, :current_user, user)}
_ ->
:error
end
end
def id(socket) do
"users_socket:#{socket.assigns.current_user.id}"
end
end定义频道:
defmodule CityWeb.DistrictChannel do
use Phoenix.Channel
alias City.Dispatch
def join("district:" <> district_id, _payload, socket) do
if Dispatch.can_join_district?(socket.assigns.current_user, district_id) do
{:ok, assign(socket, :district_id, district_id)}
else
{:error, %{reason: "forbidden"}}
end
end
def handle_in("report_incident", %{"title" => title}, socket) do
case Dispatch.report_from_channel(socket.assigns.current_user, socket.assigns.district_id, title) do
{:ok, incident} ->
broadcast!(socket, "incident_reported", %{
id: incident.id,
title: incident.title,
status: incident.status
})
{:reply, {:ok, %{id: incident.id}}, socket}
{:error, reason} ->
{:reply, {:error, %{reason: inspect(reason)}}, socket}
end
end
end客户端 JavaScript:
import {Socket} from "phoenix"
const socket = new Socket("/socket", {
params: {token: window.userToken}
})
socket.connect()
const channel = socket.channel("district:42", {})
channel.join()
.receive("ok", resp => console.log("joined", resp))
.receive("error", resp => console.error("failed", resp))
.receive("timeout", () => console.error("timeout"))
channel.on("incident_reported", payload => {
console.log("新事件", payload)
})
channel.push("report_incident", {title: "东门交通拥堵"})
.receive("ok", payload => console.log("已登记", payload))这套代码背后的核心心智是:
Socket:总无线链路channel("district:42"):切到某个具体频道join():申请入频handle_in:处理客户端发来的电文broadcast!:往本频道广播channel.on(...):客户端监听频道广播