[elixir! #0001] 初探channel,理解最簡單的聊天頻道

Channel

Channel 是 Phoenix 框架中的一種高級抽象,也是Phoenix中最激動人心的部分。它能夠方便地實現客戶端之間的軟實時通訊,今天咱們就來用它來構建一個最簡單的聊天頻道。主要目的是理解 Channel 的使用方式。html

目標

lobby

圖中的圓圈表明客戶端,圓角矩形表明一個 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 模塊。下面咱們就來實現這個模塊。函數

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! 就到這裏了,若是你以爲有哪裏不太明白的,或者發現了錯誤的地方,請務必留言!下一期咱們將繼續擴展這個聊天室的功能。

相關文章
相關標籤/搜索