檢視原始碼 Phoenix.Socket 行為 (Phoenix v1.7.14)
一個透過頻道多工處理訊息的 Socket 應用。
Phoenix.Socket
用於建立客戶端與伺服器之間連線的模組。一旦連線建立,初始狀態會儲存在 Phoenix.Socket
結構中。
相同的 Socket 可用於接收不同傳輸的事件。Phoenix 在端點呼叫 Phoenix.Endpoint.socket/3
時,支援 websocket
與 longpoll
的選項。websocket
預設開啟,而 longpoll
也能明確設定。
socket "/socket", MyAppWeb.Socket, websocket: true, longpoll: false
上述指令表示可以透過 WebSocket 連線建立進入的 Socket 連線。進入與傳出的事件透過主題路由至頻道
channel "room:lobby", MyAppWeb.LobbyChannel
更多頻道的相關資訊,請見 Phoenix.Channel
。
Socket 行為
Socket 處理器會掛載在端點中,並必須定義兩個回呼函式
connect/3
- 接收 Socket 參數、連線資訊(若有),並驗證連線。必須回傳一個Phoenix.Socket
結構,通常會包含自訂的 assignsid/1
- 接收connect/3
回傳的 Socket,並回傳此連線的識別碼(字串)。id
用於辨識 Socket 連線,通常是某個特定使用者,讓我們可以強制中斷連線。對於不需要驗證的 Socket,可以回傳nil
範例
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
channel "room:*", MyAppWeb.RoomChannel
def connect(params, socket, _connect_info) do
{:ok, assign(socket, :user_id, params["user_id"])}
end
def id(socket), do: "users_socket:#{socket.assigns.user_id}"
end
# Disconnect all user's socket connections and their multiplexed channels
MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
Socket 欄位
:id
- Socket 的字串識別碼:assigns
- Socket assigns 的對應表,預設值:%{}
:channel
- 目前的頻道模組:channel_pid
- 頻道的 PID:endpoint
- 此 Socket 來自的端點模組,例如:MyAppWeb.Endpoint
:handler
- 起始發送此 socket 之 socket 模組,例如:MyAppWeb.UserSocket
:joined
- 如果 socket 已有效加入频道:join_ref
- 加入時由使用者端發送之 ref:ref
- 由使用者端發送之最新 ref:pubsub_server
- socket 之 pubsub 伺服器之註冊名稱:topic
- 字串主題,例如"room:123"
:transport
- 傳輸之識別碼,用於記錄:transport_pid
- socket 之傳輸程序之 pid:serializer
- socket 訊息之序列化器
使用選項
在 use Phoenix.Socket
,下列選項為可接受選項
:log
- 預設層級記錄 socket 動作。預設值為:info
。可設定為false
以停用。:partitions
- 每個频道都會在一個監督之下分派。此選項控制將分派多少個監督來處理频道。預設值為處理器核心數。
垃圾回收
在處理大訊息後,可以在傳輸程序強制執行垃圾回收。例如,若要從頻道觸發此指令,請執行
send(socket.transport_pid, :garbage_collect)
另外,您可以設定 websocket 針對 :fullsweep_after
選項觸發更頻繁的完整掃描垃圾回收。有關更多資訊,請參閱 Phoenix.Endpoint.socket/3
。
用戶端與伺服器通訊
在 Phoenix.Socket.Serializer
定義,伺服器資料的編碼和用戶端資料的解碼根據序列化器完成。預設使用 JSON 編碼來仲介用戶端的訊息。
序列化器的 decode!
函數必須傳回 Phoenix.Socket.Message
,該訊息會轉發給頻道,但下列情況除外:
"heartbeat"
在 "phoenix" 主題的事件 - 應只發出 OK 回應"phx_join"
在任何主題上 - 應加入該主題"phx_leave"
在任何主題上 - 應離開該主題
每則訊息還有一個 ref
欄位,用於追蹤回應。
伺服器可以發送訊息或回應。對於訊息,ref 唯一識別訊息。對於回應,ref 與原始訊息匹配。兩種資料類型還包含一個 join_ref,可唯一識別目前加入的頻道。
Phoenix.Socket
實作也可能會發送特殊訊息和回應
"phx_error"
- 發生錯誤時,例如頻道處理程序崩潰,或嘗試加入已加入的頻道時"phx_close"
- 頻道正常關閉
Phoenix 附帶websocket 和長輪詢的 JavaScript 執行,可與 Phoenix.Socket 互動,並作為對實作自訂用戶端有興趣的人的參考。
自訂通訊端點和傳輸
請參閱 Phoenix.Socket.Transport
文件,以取得更多有關撰寫您未運用頻道的通訊端點或撰寫與其他通訊端點互動的傳輸的資訊。
自訂頻道
只要實作 child_spec/1
函式,您就可以將任何模組列為頻道。child_spec/1
函式接收呼叫者作為引數,且必須回傳一個初始化處理程序的子規格。
處理程序初始化後,會收到下一個訊息
{Phoenix.Channel, auth_payload, from, socket}
自訂頻道實作在初始化期間,必須呼叫 GenServer.reply(from, {:ok | :error, reply_payload})
,其中包含自訂 reply_payload
,該自訂 reply_payload
將作為回覆傳送給用戶端。如果未這樣做,通訊端點將永遠遭到封鎖。
自訂頻道接收 Phoenix.Socket.Message
結構作為來自傳輸的常規訊息。這些訊息的回覆和自訂訊息,可隨時透過建構適當的 Phoenix.Socket.Reply
和 Phoenix.Socket.Message
結構,使用序列化程式編碼,以及將序列化結果傳送給傳輸,來傳送至通訊端點。
例如,若要處理建議所有頻道實作的「phx_leave」訊息,可以這樣做
def handle_info(
%Message{topic: topic, event: "phx_leave"} = message,
%{topic: topic, serializer: serializer, transport_pid: transport_pid} = socket
) do
send transport_pid, serializer.encode!(build_leave_reply(message))
{:stop, {:shutdown, :left}, socket}
end
傳送到所有頻道的一個特殊訊息是事件為「phx_drain」的廣播訊息,該訊息會在應用程式關閉期間,在通訊端點耗盡時傳送。通常,處理該訊息的方式是將耗盡訊息傳送給傳輸,導致其關閉
def handle_info(
%Broadcast{event: "phx_drain"},
%{transport_pid: transport_pid} = socket
) do
send(transport_pid, :socket_drain)
{:stop, {:shutdown, :draining}, socket}
end
我們也建議所有頻道在 init
上監控 transport_pid
,且在傳輸結束時結束。我們也建議將 :normal
結束原因(通常是因為通訊端點被關閉)改寫為 {:shutdown, :closed}
,以保證連結在頻道結束時會中斷(因為 :normal
結束不會中斷連結)
def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do
reason = if reason == :normal, do: {:shutdown, :closed}, else: reason
{:stop, reason, socket}
end
除非在關閉前將 {:socket_close, pid, reason}
訊息傳送到通訊端點,否則任何處理程序結束都將被通訊端點層視為一個錯誤。
自訂頻道實作無法使用 Phoenix.ChannelTest
來測試。
摘要
類型
@type t() :: %Phoenix.Socket{ assigns: map(), channel: atom(), channel_pid: pid(), endpoint: atom(), handler: atom(), id: String.t() | nil, join_ref: term(), joined: boolean(), private: map(), pubsub_server: atom(), ref: term(), serializer: atom(), topic: String.t(), transport: atom(), transport_pid: pid() }
回呼
connect/3
的簡化版,不接收 connect_info
。
提供後向相容性。
@callback connect(params :: map(), t(), connect_info :: map()) :: {:ok, t()} | {:error, term()} | :error
接收 socket 參數並驗證連線。
Socket 參數和 assigns
Socket 參數從客戶端傳遞,可透過它驗證和認證使用者。在驗證後,你可以將預設 assigns 放進 socket,而這些 assigns 會設定至所有頻道,例如
{:ok, assign(socket, :user_id, verified_user_id)}
若要拒絕連線,請傳回 :error
或 {:error, term}
。若要控制客戶端在這種情況下收到的回應,請在 WebSocket 設定中定義錯誤處理常式。
詳細內容,請參閱Phoenix.Token
文件,當中包含如何在連線時進行代幣驗證的範例。
識別 socket 連線。
Socket ID 是頻道,你可以使用它識別特定使用者的所有 socket
def id(socket), do: "users_socket:#{socket.assigns.user_id}"
讓你廣播一個 "disconnect"
事件,並終止某個使用者的所有 active socket 和頻道
MyAppWeb.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
傳回 nil
會讓這個 socket 變成匿名。
函式
將 key-value 對新增至 socket 的 assigns。
可以傳入單一 key-value 對,可以提供關鍵字清單或 assigns 的地圖與現有的 socket assigns 合併。
範例
iex> assign(socket, :name, "Elixir")
iex> assign(socket, name: "Elixir", logo: "💧")
定義一個與給定主題和傳輸相符的頻道。
topic_pattern
- 字串模式,例如"room:*"
、"users:*"
或"system"
module
- 頻道模組處理常式,例如MyAppWeb.RoomChannel
opts
- 選擇性選項清單,詳見下方
選項
:assigns
- socket 指派之 map,在加入時併入 socket
範例
channel "topic1:*", MyChannel
主題模式
channel
巨集接受兩種主題模式。星號(*
字元)引數可做為最後一個字元,用於表示 "主題:子主題"
比對。如果提供純文字字串,只有該主題可比對頻道處理常式。大多數案例會使用 "主題:*"
模式,以允許更靈活的主題範圍。
有關更多資訊,請參閱 Phoenix.Channel