在另外一篇文章小編介紹了怎麼透過非同步 AJAX 的方式跟伺服器進行溝通,那需要即時的同步溝通怎麼辦?
這篇文章接著會介紹可以做到網頁即時通訊服務的技術:
API 在系統設計上是為了溝通而產生的,而非同步溝通技術較為簡單且容易實作,即時通訊則需要較多層面的情境與技術考量。
以男女之間來說,非同步的溝通比較容易造就產生時間管理大師,即時通訊則相對較為困難。
即時通訊原理
在 2010 年 Chrome 開始支援了新的即時通訊 API 後 Web App 開始走向全新的時代,概念上從雙方通訊的方式可以分成下面三種:
- 單工:訊號只在一個方向上進行傳遞,像是寫情書給喜歡的女生
- 雙工: 允許雙向資料傳輸,像是曖昧期的相處
- 半雙工:可切換方向的單工通訊,像是只有一方有意思的時候,訊息通常是單向的
- 全雙工:現在即時通訊,即將熱戀中的男女,雙方同時接收或是傳送訊息
非同步溝通: 屬於單工或半雙工,注重的會是資訊的 “傳入” 以及 “傳出”
即時通訊: 全雙工,注重的會是 “監聽事件” 以及 “發出事件”
Long-Polling
從翻譯來看就是比較長的 Polling,用途其實是以舊的 AJAX 技術模擬即時通訊的效果。
- Polling: 前端向後端發出請求,如果沒拿到想要的資料就重發,伺服器附載較重
- Long-Polling:
- Client 發 Request 給 Server
- Server 送 Response 給 Client 後才斷開連線 (降低伺服器負擔但占用連線數)
- Client 收到後再發 Request 給 Sevrer
Polling 像是奪命連環 Call 會一直 Call 到有反應為止,就像正妹的 Line 打開永遠都是 999+ 未讀未接一樣,負擔其實很大。
Long-Polling 會是優化版本的,正妹雖然有好幾個通訊軟體,雖然打 Line 過去後被 Mute (因為切去用 Messenger),但至少可以確定之後會回。
以下為 Long-Polling 實作範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| let timeout;
function valueChanged(value) { return (dispatch) => { dispatch(loadSuggestionsInProgress()); dispatch({ type: "VALUE_CHANGED", payload: { value, }, });
timeout && clearTimeout(timeout);
timeout = setTimeout( () => { axios .get(`/suggestions?q=${value}`) .then((response) => dispatch(loadSuggestionsSuccess(response.data.suggestions)) ) .catch(() => dispatch(loadSuggestionsFailed())); }, 1000, value ); }; }
|
Server Sent Events 實作
網頁一般來說是由客戶端向伺服器請求資料。
藉由 server-sent 事件, 伺服器在任何時候都可以向客戶端推送資料,推送進來的訊息可以在客戶端上做事件與資料的處理。
另外一種方式是使用Service Worker 可透過 PushManager 一起搭配實作離線推播,不過為獨立 Thread 無法操作 dom。
Server Sent Events 比較像是正妹找工具人的概念,工具人們都會等待正妹的指令,指令一下就會進行動作。
以下為 Server Sent Events 實作範例程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| let clients = []; const server = express();
function sendEventsToClients() { clients.forEach((c) => { c.res.write(`change`); }); }
async function modify(req, res) { res.json({ status: 1 }); return sendEventsToClients(); }
server.get("/modify", modify); server.get("/listen", (req, res) => { const { clientId } = req.query; const headers = { "Content-Type": "text/event-stream", Connection: "keep-alive", "Cache-Control": "no-cache", }; res.writeHead(200, headers); res.write(""); const newClient = { id: clientId, res, }; clients.push(newClient); req.on("close", () => { clients = clients.filter((c) => c.id !== clientId); }); });
const events = new EventSource("/listen?clientId=test"); events.onmessage = (event) => { if (event.data) console.log(event.data); };
events.addEventListener("ping", function (event) { if (event.data) console.log(event.data); });
|
WebSocket
WebSocket 這個 API 在不必 polling 伺服器的情況下,讓用戶傳送訊息至伺服器並接受事件驅動回應,達到即時通訊的效果。
接近真的談戀愛的溝通,雙方各自在沒什麼負擔的情況下進行訊息的交流。
Socket.IO 簡介
Socket.IO 屬於 node.js 解決方案,封裝了 Long-Polling 及 WebSocket,是一個 event-based 全雙工的通訊函式庫,事件驅動這個部分是最容易出現效能的地方。
當和 react 專案整合時,需要注意事件是否影響畫面渲染,避免每渲染一次就重新建立連結、重新監聽事件、重新發出訊息,記憶體很快就會用完,處理器來不及處理。
後端的基本範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require("express"); const app = express(); const http = require("http"); const server = http.createServer(app); const { Server } = require("socket.io"); const io = new Server(server);
app.get("/", (req, res) => { res.sendFile(__dirname + "/index.html"); });
io.on("connection", (socket) => { console.log("a user connected"); socket.on("message", (msg) => { console.log("message: " + msg); }); });
server.listen(3000, () => { console.log("listening on *:3000"); });
|
前端的基本範例:
1 2 3 4 5 6 7 8 9 10 11 12
| var socket = io();
var form = document.getElementById("form"); var input = document.getElementById("input");
form.addEventListener("submit", function (e) { e.preventDefault(); if (input.value) { socket.emit("message", input.value); input.value = ""; } });
|
MQTT
MQTT (Message Queuing Telemetry Transport) 適合輕量級物聯網使用,封包較小可以支援大量的 client。
主要是基於 subscribe 跟 publish 兩個概念的協定,為了硬體效能低下的遠端裝置以及網路狀況糟糕的情況下而設計。
主要是以 TCP/IP 協定上去優化且取代 HTTP 這種較肥的資料傳輸協定,因此會需要一個訊息中介軟體 (MQTT Broker) 來提供輕量化的解決方案。
底下官方範例會是使用官方提供的 MQTT Broker mqtt://test.mosquitto.org
,若為自己的服務需要自行架設。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <html> <head> <title>test Ws mqtt.js</title> <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script> </head> <body> <script> const mqtt = require("mqtt"); const client = mqtt.connect("mqtt://test.mosquitto.org");
client.on("connect", function () { client.subscribe("presence", function (err) { if (!err) { client.publish("presence", "Hello mqtt"); } }); });
client.on("message", function (topic, message) { console.log(message.toString()); client.end(); }); </script> </body> </html>
|
喜歡這篇文章,請幫忙拍拍手喔 🤣