Channel 是 Phoenix 框架中的一種高級抽象,也是Phoenix中最激動人心的部分。它能夠方便地實現客戶端之間的軟實時通訊,今天咱們就來用它來構建一個最簡單的聊天頻道。主要目的是理解 Channel 的使用方式。html
圖中的圓圈表明客戶端,圓角矩形表明一個 Channel ,room:lobby
是Channel的名稱,又叫 topic ,以 topic:subtopic
的形式表示。在這個 Channel 中,每一個客戶端發出的消息均可以被全部成員看到。web
首先,客戶端發送一個請求加入頻道的信息給服務器。服務器中的 socket handler 套接字處理器接受了這個請求,並根據服務器中所定義的 Channel Route 頻道路徑來進入相應的頻道。Transport 層表明頻道中具體的通訊手段,默認是 Web Socket。PubSub 層中定義了 Channel 裏的各類行爲,例如關注某個話題,取消關注,發佈廣播等,通常不會修改這一層。服務器
首先,咱們使用 mix phoenix.new monkey
新建一個名爲 monkey 的 Phoenix 應用。安裝好依賴以後,打開 monkey 文件夾。app
在 lib/monkey/endpoint.ex
文件中,能夠看到對應的 socket 已經設置好:框架
defmodule Monkey.Endpoint do use Phoenix.Endpoint, otp_app: :monkey socket "/socket", Monkey.UserSocket
咱們打開 web/channels/user_socket.ex
, UserSocket 模塊就是在這裏定義的。將其中 Channels 下一行的註釋取消:socket
defmodule Monkey.UserSocket do use Phoenix.Socket ## Channels channel "room:*", Monkey.RoomChannel
這裏的 room:*
表示全部 topic 爲 room 的頻道請求都會調用 RoomChannel 模塊。下面咱們就來實現這個模塊。函數
在 web/channels/room_channel.ex
中寫入以下內容:spa
defmodule Monkey.RoomChannel do use Monkey.Web, :channel intercept ["new_msg"] def join("room:lobby", _message, socket) do {:ok, socket} end def join("room:" <> _private_room_id, _params, _socket) do {:error, %{reason: "unauthorized"}} end def handle_in("new_msg", %{"body" => body}, socket) do broadcast! socket, "new_msg", %{body: body} {:noreply, socket} end def handle_out("new_msg", payload, socket) do push socket, "new_msg", payload {:noreply, socket} end end
在 web/static/js/socket.js
中作以下修改:3d
socket.connect() // Now that you are connected, you can join channels with a topic: let channel = socket.channel("room:lobby", {}) let chatInput = document.querySelector("#chat-input") let messagesContainer = document.querySelector("#messages") chatInput.addEventListener("keypress", event => { if(event.keyCode === 13){ channel.push("new_msg", {body: chatInput.value}) chatInput.value = "" } }) channel.on("new_msg", payload => { let messageItem = document.createElement("li"); messageItem.innerText = `[${Date()}] ${payload.body}` messagesContainer.appendChild(messageItem) }) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp) }) .receive("error", resp => { console.log("Unable to join", resp) }) export default socket
把 web/static/js/app.js
中 // import socket from "./socket"
這一行的註釋取消。code
將 web/templates/page/index.html.eex
中的內容修改成:
<div id="messages"></div> <input id="chat-input" type="text"></input>
首先,咱們定義了 join/3
函數,用來處理客戶端的進入請求。當客戶端請求進入聊天大廳 room:lobby
時,返回 {:ok, socket}
, 當 subtopic 爲其它值時,返回{:error, %{reason: "unauthorized"}}
。咱們在js文件中將頻道名默認設置爲 room:lobby
,因此這條從句暫時不會起做用。
handle_in/3
函數定義了接收到新消息時的行爲,它會調用 broadcast!/3
函數,而該函數又須要經過 handle_out/3
函數。 handle_out/3
的做用至關於一個過濾器,咱們能夠在其中設置哪些消息不能發送出去。在這裏咱們沒有用到過濾功能,只是直接將接收到的消息push 到socket。
當socket接收到了 "new_msg", payload
,便會在messagesContainer
中新建一個內容爲新消息的li標籤。
這一期的elixir! 就到這裏了,若是你以爲有哪裏不太明白的,或者發現了錯誤的地方,請務必留言!下一期咱們將繼續擴展這個聊天室的功能。