從 WebSocket 開始:用即時通訊打造即時數據推送實戰指南

大家在做過「告警管理」或「情報運營大屏」這類功能時,一定有過這種困惑:為什麼頁面上的數據總是慢半拍?點了查詢才刷新,不點就停在那裡。這背後其實是 HTTP 協議本身的特性——它是「請求—回應」模式,客戶端不喊話,服務端就不會主動說話。當你需要即時推送(例如新告警彈出、3D 地球熱點閃爍),傳統輪詢既浪費頻寬又延遲明顯。這篇文章要帶你認識 WebSocket:一條「長連接」的雙向通道,讓服務端能主動把數據推到前端。

一、WebSocket 與 HTTP 輪詢的差異

想像 HTTP 輪詢像你每 5 秒打一次電話問「有新消息嗎?」;而 WebSocket 是通一次電話後兩人一直線上,誰有消息隨口就說。兩者關鍵區別:

  • 握手階段:WebSocket 先透過一次 HTTP 升級(Upgrade)握手,之後協議切換,連接保持打開。
  • 雙向性:握手後客戶端與服務端都能主動發送訊息,不再是單向請求。
  • 開銷:每條訊息沒有重複的 HTTP 頭,適合高頻小數據(即時行情、告警流)。

重點:WebSocket 並非要取代 HTTP,而是補足「即時推送」場景。靜態資源、表單提交仍用 REST 即可。

二、步驟 1:建立最小 WebSocket 服務端(Node.js)

ws 庫為例,幾行就能起一個回聲+主動推送的服務:

// server.js
import { WebSocketServer } from 'ws';

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', (ws) => {
  console.log('客戶端已連線');
  // 服務端主動每 3 秒推送一筆行情
  const timer = setInterval(() => {
    ws.send(JSON.stringify({
      type: 'tick',
      price: (Math.random() * 100).toFixed(2),
      ts: Date.now(),
    }));
  }, 3000);

  ws.on('message', (msg) => {
    console.log('收到客戶端訊息:', msg.toString());
  });

  ws.on('close', () => clearInterval(timer));
});

console.log('WebSocket 服務監聽在 ws://localhost:8080');

三、步驟 2:前端用瀏覽器原生 API 連接

瀏覽器內建 WebSocket 物件,無須額外套件:

const ws = new WebSocket('ws://localhost:8080');

ws.onopen = () => {
  console.log('連線建立');
  ws.send(JSON.stringify({ type: 'subscribe', symbol: 'FUTU' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('收到推送:', data);
  // 在這裡更新 Vue 的響應式狀態或 Pinia store
};

ws.onerror = (err) => console.error('連線錯誤', err);
ws.onclose = () => console.log('連線關閉');

四、步驟 3:處理斷線重連與心跳

生產環境最容易被忽略的就是「連線會掉」。要加上重連與心跳保活:

function connect() {
  const ws = new WebSocket('ws://localhost:8080');
  let heartbeat;

  ws.onopen = () => {
    heartbeat = setInterval(() => ws.send('ping'), 15000); // 心跳
  };
  ws.onclose = () => {
    clearInterval(heartbeat);
    setTimeout(connect, 3000); // 3 秒後重連
  };
  return ws;
}
connect();

重點:心跳間隔要小於代理/負載均衡的空閒超時(常見 60 秒),否則連線會被中間層悄悄切斷。

五、步驟 4:在 Vue 3 中封裝成可複用 composable

把連線邏輯收進 useWebSocket,元件只管拿數據:

// useWebSocket.ts
import { ref, onUnmounted } from 'vue';

export function useWebSocket(url: string) {
  const lastMessage = ref<any>(null);
  const ready = ref(false);
  let ws: WebSocket;

  function connect() {
    ws = new WebSocket(url);
    ws.onopen = () => (ready.value = true);
    ws.onmessage = (e) => (lastMessage.value = JSON.parse(e.data));
    ws.onclose = () => setTimeout(connect, 3000);
  }
  connect();

  onUnmounted(() => ws.close());
  return { lastMessage, ready };
}

六、部署注意事項

若你的前端走 Nginx 反向代理,需開啟 WebSocket 升級支援:

location /ws/ {
  proxy_pass http://backend;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
}

重點:Connection "upgrade" 這行是關鍵,漏掉就會握手失敗。另外注意 13zn.com 這類共享主機環境不支援長連接,即時通訊類專案需自備支援 WebSocket 的後端環境。

互動話題:你在專案裡用過 WebSocket 嗎?是從輪詢遷移過來,還是一開始就設計即時推送?遇到最頭痛的坑是什麼?歡迎在留言區分享你的經驗!