cover

概念概覽

sequenceDiagram
    participant Client as 客戶端
    participant Server as 伺服器

    rect rgb(240, 240, 255)
        Note over Client,Server: 傳統 HTTP(請求-回應)
        Client->>Server: HTTP Request
        Server->>Client: HTTP Response
        Client->>Server: HTTP Request
        Server->>Client: HTTP Response
    end

    rect rgb(240, 255, 240)
        Note over Client,Server: WebSocket(全雙工)
        Client->>Server: HTTP Upgrade 請求
        Server->>Client: 101 Switching Protocols
        Client->>Server: 訊息
        Server->>Client: 訊息
        Server->>Client: 主動推送
        Client->>Server: 訊息
    end

什麼是 WebSocket?

想像一下:傳統的 HTTP 就像打電話給客服——你問一個問題、對方回答、然後掛斷,下次有問題要再打一通。但如果你需要的是像 LINE 聊天那樣的即時對話呢?WebSocket 就是讓你跟伺服器之間「保持通話不掛斷」的技術。這也是為什麼你在 Slack 或 Discord 上發訊息時,對方能瞬間收到,而不是等幾秒才刷新出來。

WebSocket 是一種網路通訊協定,它提供了全雙工(Full-Duplex) 的通訊管道,讓伺服器和客戶端可以在單一 TCP 連線上同時雙向傳輸資料。

為什麼需要 WebSocket?

傳統的 HTTP 協定是「請求-回應」模式:

  • 客戶端發送請求
  • 伺服器回應資料
  • 連線結束

這種模式的問題是:伺服器無法主動推送資料給客戶端

如果要實現即時更新,傳統做法是:

  1. 輪詢(Polling):客戶端每隔幾秒發一次請求 → 浪費資源
  2. 長輪詢(Long Polling):伺服器保持連線直到有新資料 → 還是不夠即時

WebSocket 解決了這個問題,建立連線後,雙方可以隨時互相發送訊息。

WebSocket 運作原理

1. 握手階段(Handshake)

WebSocket 連線從一個 HTTP 請求開始:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

伺服器同意後回應:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

2. 資料傳輸

握手成功後,連線從 HTTP 升級為 WebSocket 協定,雙方可以自由發送訊息。

3. 連線關閉

任一方都可以發送關閉訊號結束連線。

前端使用範例

// 建立連線
const socket = new WebSocket('wss://example.com/socket');
 
// 連線成功
socket.onopen = () => {
  console.log('已連線');
  socket.send('Hello Server!');
};
 
// 收到訊息
socket.onmessage = (event) => {
  console.log('收到:', event.data);
};
 
// 連線關閉
socket.onclose = () => {
  console.log('連線已關閉');
};
 
// 發生錯誤
socket.onerror = (error) => {
  console.error('WebSocket 錯誤:', error);
};

常見應用場景

場景說明
即時聊天LINE、Slack、Discord 的訊息即時顯示
協作編輯Google Docs 多人同時編輯
股票行情即時更新股價變動
遊戲多人連線遊戲的狀態同步
通知推播網站即時通知
IoT 監控感測器資料即時回傳

WebSocket vs HTTP

特性HTTPWebSocket
連線方式短連線(請求-回應)長連線(持續保持)
通訊方向單向(客戶端發起)雙向(雙方都可發起)
延遲較高極低
資源消耗每次請求都要建立連線建立一次,持續使用
適用場景一般網頁請求即時互動應用

相關技術

  • Socket.IO:封裝 WebSocket 的 JavaScript 函式庫,提供自動重連、房間等功能
  • Server-Sent Events (SSE):伺服器單向推送,比 WebSocket 簡單但只能單向
  • gRPC:Google 的 RPC 框架,也支援串流通訊

動手做:即時聊天室 Mini Project

看了這麼多概念,不如直接寫一個能跑的聊天室來體會 WebSocket 的威力。這個 demo 只需要 Node.js 和瀏覽器,5 分鐘就能跑起來。

Server 端(Node.js + ws)

// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
 
const clients = new Set();
 
wss.on('connection', (ws) => {
  clients.add(ws);
  console.log(`新連線,目前 ${clients.size} 人在線`);
 
  // 廣播給所有人
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify({
          user: data.user,
          text: data.text,
          time: new Date().toLocaleTimeString()
        }));
      }
    });
  });
 
  ws.on('close', () => {
    clients.delete(ws);
    console.log(`斷線,剩餘 ${clients.size} 人在線`);
  });
});
 
console.log('WebSocket Server 啟動在 ws://localhost:8080');

Client 端(純 HTML + JS)

<!DOCTYPE html>
<html>
<body>
  <div id="messages" style="height:300px;overflow-y:auto;border:1px solid #ccc;padding:10px;margin-bottom:10px;"></div>
  <input id="name" placeholder="你的名字" style="width:100px" />
  <input id="input" placeholder="輸入訊息..." style="width:300px" />
  <button onclick="send()">送出</button>
  <script>
    const ws = new WebSocket('ws://localhost:8080');
    const messages = document.getElementById('messages');
 
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      messages.innerHTML += `<p><b>${data.user}</b> [${data.time}]: ${data.text}</p>`;
      messages.scrollTop = messages.scrollHeight;
    };
 
    ws.onopen = () => messages.innerHTML += '<p style="color:green">✅ 已連線</p>';
    ws.onclose = () => messages.innerHTML += '<p style="color:red">❌ 已斷線</p>';
 
    function send() {
      const name = document.getElementById('name').value || '匿名';
      const text = document.getElementById('input').value;
      if (text) {
        ws.send(JSON.stringify({ user: name, text }));
        document.getElementById('input').value = '';
      }
    }
 
    document.getElementById('input').addEventListener('keypress', (e) => {
      if (e.key === 'Enter') send();
    });
  </script>
</body>
</html>

跑起來試試

# 1. 初始化專案
mkdir ws-chat && cd ws-chat
npm init -y
npm install ws
 
# 2. 建立 server.js(貼上面的 code)
 
# 3. 啟動 server
node server.js
 
# 4. 開兩個瀏覽器分頁,打開 index.html
# 5. 兩邊都能即時看到對方的訊息!

這個 demo 展示了什麼

回頭看本文介紹的核心概念,這個小聊天室其實全都用上了:

  • 全雙工通訊:Server 可以主動 push 訊息給所有 client
  • 持久連線:連線建立後不會斷,不用每次都重新握手
  • 廣播模式:一個人發訊息,所有人都收到

下一步

如果要做成正式產品,還需要:

  • 心跳機制(ping/pong)防止連線 timeout
  • 重連邏輯(斷線後自動重連)
  • 房間機制(不是所有人都收到)
  • 認證(連線時驗證 token)

這些概念在本文的其他章節都有介紹。


延伸閱讀

阮一峰的网络日志 - WebSocket 教程 MDN - WebSocket API 概念 RESTFul API