iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
1

Phoenix 在最初設計時,其中一個重要的目標就是想解決高併發的訊息傳送情境。雖然 Erlang / Elixir 原本就有 message passing 的機制,但由於 message passing 主要應用在一對一發送訊息的情況,而高併發的訊息傳送常有一對多訊息發送的需求,因此這個功能是用 PubSub 的概念實作。

快速用模版生成 Channel

由於 Channel 牽涉到的元件比較多,我們這次試著反過來試試看。在開始扯淡解釋之前,先試著生成一個 Channel。就算每個步驟不太知道在做什麼也沒關係,之後再來跟著解釋看會比較理解。首先在 shell 裡切換到我們的專案目錄下,輸入以下的指令:

$ mix phx.gen.channel room

* creating lib/hello_phx_web/channels/room_channel.ex
* creating test/hello_phx_web/channels/room_channel_test.exs

Add the channel to your `lib/hello_phx_web/channels/user_socket.ex` handler, for example:

    channel "room:lobby", HelloPhxWeb.RoomChannel

後端: user_socket.ex

在生成兩個檔案後,上面有提示要在 lib/hello_phx_web/channels/user_socket.ex 裡加一行程式碼,但打開檔案會發現已經存在一行範圍更寬的條件,所以把該行反註釋就好

# lib/hello_phx_web/channels/user_socket.ex
defmodule HelloPhxWeb.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "room:*", HelloPhxWeb.RoomChannel # <= 反註釋這行

前端: socket.js

要讓瀏覽器可以使用 JavaScript 與後端直接用 websocket 交換訊息,我們也需要處理前端的部份。好在大部份的程式也都先預寫好了。打開 assets/js/socket.js。移到最下面會發現一段已經寫好的 socket.connect() 的程式。在底下把 socket.channel 的第一個參數改成 “room:lobby”:

// assets/js/socket.js
socket.connect()

let channel = socket.channel("room:lobby", {}) // <= 改這行
channel.join()
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

前端: app.js

而因為剛才的 socket.js 預設不會被打包進最終的 JavaScript asset 裡,要打開 assets/js/app.js 反註釋最後一行:

// assets/js/app.js
import "phoenix_html"

// Local files can be imported directly using relative
// paths "./socket" or full ones "web/static/js/socket".

import socket from "./socket" // <= 反註釋這行

中場測試

一樣用 mix phx.server 將網站應用程式跑起來,用瀏覽器打開 http://localhost:4000。接著我們要把開發者工具打開,如果是 Google Chrome 的話,就按下 [Option] + [Cmd] + [J],你應該會在底下看到一行 ”Joined successfully”,這表示之前的步驟都是對的。如果沒有的話,請重新確認之前的步驟再往下喔。

https://ithelp.ithome.com.tw/upload/images/20180117/20103390IK6apHorxF.png

聊天室功能

至此我們已經把前後端的 websocket 通路接起來了,接著要實作讓使用者聊天的功能。

在 template 裡加上顯示及發送訊息的介面

打開 lib/hello_phx_web/templates/page/index.html.eex,在最下方加入兩行 HTML,其中第一行是用來放顯示的訊息,第二行是用來發送訊息的文字框:

<! ... >
</div>

<div id="messages"></div>
<input id="chat-input" type="text"></input>

輸入新訊息

打開 assets/js/socket.js,加入底下的程式碼。這段的功能是在輸入框輸入文字,並按下 [Enter] 後,會發送 websocket 訊息到後端,並清空輸入框。這裡要注意的是它會用 channel.push("shout", 訊息內容) 將訊息發送到後端。

let channel           = socket.channel("room:lobby", {})

/** 加入這段:發送新訊息 **/
let chatInput         = document.querySelector("#chat-input")
chatInput.addEventListener("keypress", event => {
  if(event.keyCode === 13){
    channel.push("shout", {body: chatInput.value})
    chatInput.value = ""
  }
})
/** 結束 **/

channel.join()
//...

顯示新訊息

一樣在 assets/js/socket.js 中剛剛那段程式碼的下方,再加進這段程式碼,這樣每次從 websocket channel 中接收到 “shout” 訊息時,會把內容加進顯示的區塊裡。

  chatInput.value = ""
  }
})

/** 加入這段:顯示新訊息 **/
let messagesContainer = document.querySelector("#messages")

channel.on("shout", payload => {
  let messageItem = document.createElement("li");
  messageItem.innerText = `[${Date()}] ${payload.body}`
  messagesContainer.appendChild(messageItem)
})
/** 結束 **/
channel.join()

成品測試

好的,實作的部份都完成了,可以來試玩一下了。先確認你的 mix phx.server 依然在運作中,接著打開兩個瀏覽器,都連到我們的 http://localhost:4000。在其中一個瀏覽器的輸入框輸入任何訊息並按下 [Enter]後,在兩個瀏覽器裡都會出現該筆訊息:

https://ithelp.ithome.com.tw/upload/images/20180117/20103390vgjeHct8GI.png

重點回顧

  • Channel 是 Phoenix 裡的 websocket 內建解決方案
  • mix phx.gen.channel 名稱 可以快速生成 Channel 後端檔案
  • 我們新增/修改了這些檔案
    • channels/room_channel.ex
    • channels/user_socket.ex
    • assets/js/socket.js
    • assets/js/app.js
    • templates/page/index.html.eex
  • 真的會動耶

下一篇要來解釋這一切究竟是怎麼一回事。
Happy hacking! 明天見。


上一篇
測試與文件,and one more thing…
下一篇
Channel.part_2
系列文
函數式編程: 從 Elixir & Phoenix 入門。31

1 則留言

0
taiansu
iT邦新手 5 級 ‧ 2018-01-17 20:47:25

已更新

我要留言

立即登入留言