iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0

在另外一篇文章小編介紹了透過非同步 AJAX 的方式跟伺服器進行溝通,那需要即時的同步溝通怎麼辦?

這篇文章接著會介紹可以做到網頁即時通訊服務的技術:

  • Long-Polling: 長時間輪詢
  • Server Sent Events: 伺服器傳送事件
  • WebSocket: 全雙工通訊
  • Forever Frame: IE only,嵌入一個 IFrame,連向 SignalR 提供的內容

API 在系統設計上是為了溝通而產生的,而非同步溝通技術較為簡單且容易實作,即時通訊則需要較多層面的情境與技術考量。

以男女之間來說,非同步的溝通比較容易造就產生時間管理大師,即時通訊則相對較為困難。

即時通訊原理

在 2010 年 Chrome 開始支援了新的即時通訊 API 後 Web App 開始走向全新的時代,概念上從雙方通訊的方式可以分成下面三種:

  • 單工:訊號只在一個方向上進行傳遞,像是寫情書給喜歡的女生
  • 雙工: 允許雙向資料傳輸,像是曖昧期的相處
    • 半雙工:可切換方向的單工通訊,像是只有一方有意思的時候,訊息通常是單向的
    • 全雙工:現在即時通訊,即將熱戀中的男女,雙方同時接收或是傳送訊息

非同步溝通: 屬於單工或半雙工,注重的會是資訊的 "傳入" 以及 "傳出"
即時通訊: 全雙工,注重的會是 "監聽事件" 以及 "發出事件"

Long-Polling

從翻譯來看就是比較長的 Polling,用途其實是以舊的 AJAX 技術模擬即時通訊的效果。

  • Polling: 前端向後端發出請求,如果沒拿到想要的資料就重發,伺服器附載較重
  • Long-Polling:
    1. Client 發 Request 給 Server
    2. Server 送 Response 給 Client 後才斷開連線 (降低伺服器負擔但占用連線數)
    3. Client 收到後再發 Request 給 Sevrer

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 Events 實作

網頁一般來說是由客戶端向伺服器請求資料。

藉由 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

WebSocket 這個 API 在不必 polling 伺服器的情況下,讓用戶傳送訊息至伺服器並接受事件驅動回應,達到即時通訊的效果。

接近真的談戀愛的溝通,雙方各自在沒什麼負擔的情況下進行訊息的交流。

Socket.IO 簡介

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

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>

上一篇
前端 AJAX 全攻略 (15)
下一篇
用 HTTP Cookies 記住你的曾經 (17)
系列文
前端三分鐘 X 從把妹角度理解前後端如何和平相處30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
eric19740521
iT邦新手 1 級 ‧ 2022-10-01 04:44:41

這幾個都玩過了...websocket比較好用

的確,不過每個場合還是有各自適合的方法囉XD

最近對於UDP協定用法.有新的體會
https://youtu.be/cJl13yZkDgc
這是我製作的影片...分享給你參考

感謝分享

我要留言

立即登入留言