在另外一篇文章小編介紹了透過非同步 AJAX 的方式跟伺服器進行溝通,那需要即時的同步溝通怎麼辦?
這篇文章接著會介紹可以做到網頁即時通訊服務的技術:
API 在系統設計上是為了溝通而產生的,而非同步溝通技術較為簡單且容易實作,即時通訊則需要較多層面的情境與技術考量。
以男女之間來說,非同步的溝通比較容易造就產生時間管理大師,即時通訊則相對較為困難。
在 2010 年 Chrome 開始支援了新的即時通訊 API 後 Web App 開始走向全新的時代,概念上從雙方通訊的方式可以分成下面三種:
非同步溝通: 屬於單工或半雙工,注重的會是資訊的 "傳入" 以及 "傳出"
即時通訊: 全雙工,注重的會是 "監聽事件" 以及 "發出事件"
從翻譯來看就是比較長的 Polling,用途其實是以舊的 AJAX 技術模擬即時通訊的效果。
Polling 像是奪命連環 Call 會一直 Call 到有反應為止,就像正妹的 Line 打開永遠都是 999+ 未讀未接一樣,負擔其實很大。
Long-Polling 會是優化版本的,正妹雖然有好幾個通訊軟體,雖然打 Line 過去後被 Mute (因為切去用 Messenger),但至少可以確定之後會回。
以下為 Long-Polling 實作範例程式碼:
let timeout;
function valueChanged(value) {
return (dispatch) => {
dispatch(loadSuggestionsInProgress());
dispatch({
type: "VALUE_CHANGED",
payload: {
value,
},
});
// 一秒內有值改變就清除且在重設 timeout
timeout && clearTimeout(timeout);
// 一秒後再打一次
timeout = setTimeout(
() => {
axios
.get(`/suggestions?q=${value}`)
.then((response) =>
dispatch(loadSuggestionsSuccess(response.data.suggestions))
)
.catch(() => dispatch(loadSuggestionsFailed()));
},
1000,
value
);
};
}
網頁一般來說是由客戶端向伺服器請求資料。
藉由 server-sent 事件, 伺服器在任何時候都可以向客戶端推送資料,推送進來的訊息可以在客戶端上做事件與資料的處理。
另外一種方式是使用Service Worker 可透過 PushManager 一起搭配實作離線推播,不過為獨立 Thread 無法操作 dom。
Server Sent Events 比較像是正妹找工具人的概念,工具人們都會等待正妹的指令,指令一下就會進行動作。
以下為 Server Sent Events 實作範例程式碼
// Server
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);
});
});
// client
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 這個 API 在不必 polling 伺服器的情況下,讓用戶傳送訊息至伺服器並接受事件驅動回應,達到即時通訊的效果。
接近真的談戀愛的溝通,雙方各自在沒什麼負擔的情況下進行訊息的交流。
Socket.IO 屬於 node.js 解決方案,封裝了 Long-Polling 及 WebSocket,是一個 event-based 全雙工的通訊函式庫,事件驅動這個部分是最容易出現效能的地方。
當和 react 專案整合時,需要注意事件是否影響畫面渲染,避免每渲染一次就重新建立連結、重新監聽事件、重新發出訊息,記憶體很快就會用完,處理器來不及處理。
後端的基本範例:
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");
});
前端的基本範例:
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 (Message Queuing Telemetry Transport) 適合輕量級物聯網使用,封包較小可以支援大量的 client。
主要是基於 subscribe 跟 publish 兩個概念的協定,為了硬體效能低下的遠端裝置以及網路狀況糟糕的情況下而設計。
主要是以 TCP/IP 協定上去優化且取代 HTTP 這種較肥的資料傳輸協定,因此會需要一個訊息中介軟體 (MQTT Broker) 來提供輕量化的解決方案。
底下官方範例會是使用官方提供的 MQTT Broker mqtt://test.mosquitto.org
,若為自己的服務需要自行架設。
<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) {
// message is Buffer
console.log(message.toString());
client.end();
});
</script>
</body>
</html>
這幾個都玩過了...websocket比較好用