Skip to content

WebSocket 基础

WebSocket 是 HTML5 引入的全双工通信协议,允许浏览器和服务器之间建立持久连接,实现双向实时数据传输。与传统的 HTTP 请求-响应模式不同,WebSocket 连接建立后,双方可以随时主动发送数据,无需重复建立连接,极大提升了实时通信的效率。

前置知识

阅读本节前,建议先了解:Service Worker

基础概念

WebSocket vs HTTP

特性HTTPWebSocket
通信方向单向(请求-响应)双向(全双工)
连接方式短连接,每次请求新建持久连接,一次握手
实时性需要轮询或长轮询天然实时
开销每次请求带完整 HTTP 头建立后仅传输数据帧
协议http:// / https://ws:// / wss://
数据格式文本(HTML/JSON等)文本或二进制
状态码HTTP 状态码自定义

WebSocket 协议

协议说明端口
ws://非加密 WebSocket默认 80
wss://加密 WebSocket(TLS)默认 443

注意

生产环境必须使用 wss://(加密 WebSocket),ws:// 仅用于开发环境。

语法与 API

创建 WebSocket 连接

javascript
// 创建 WebSocket 连接
const ws = new WebSocket('wss://echo.websocket.org');

// 创建带子协议的连接
const ws2 = new WebSocket('wss://example.com/ws', ['soap', 'json']);

WebSocket 就绪状态

javascript
// WebSocket 有四种就绪状态
const ws = new WebSocket('wss://example.com/ws');

// readyState 属性
switch (ws.readyState) {
  case WebSocket.CONNECTING:    // 0 - 正在连接
    console.log('正在建立连接...');
    break;
  case WebSocket.OPEN:           // 1 - 连接已建立
    console.log('连接已建立,可以通信');
    break;
  case WebSocket.CLOSING:        // 2 - 正在关闭
    console.log('连接正在关闭...');
    break;
  case WebSocket.CLOSED:         // 3 - 连接已关闭
    console.log('连接已关闭');
    break;
}

// 使用常量
ws.readyState === WebSocket.OPEN; // true 表示连接已建立
状态常量说明
CONNECTINGWebSocket.CONNECTING0正在建立连接
OPENWebSocket.OPEN1连接已建立
CLOSINGWebSocket.CLOSING2正在关闭
CLOSEDWebSocket.CLOSED3连接已关闭

发送数据

javascript
const ws = new WebSocket('wss://example.com/ws');

ws.onopen = () => {
  // 发送文本数据
  ws.send('Hello Server');
  ws.send('{"type":"greet","message":"你好"}');

  // 发送二进制数据
  const buffer = new ArrayBuffer(8);
  const view = new Int32Array(buffer);
  view[0] = 42;
  ws.send(buffer);

  // 发送 Blob
  const blob = new Blob(['hello']);
  ws.send(blob);

  // 发送 ArrayBuffer(TypedArray)
  const typedArray = new Uint8Array([1, 2, 3, 4]);
  ws.send(typedArray.buffer);
};

接收数据

javascript
ws.onmessage = (event) => {
  // 判断数据类型
  if (typeof event.data === 'string') {
    console.log('收到文本:', event.data);
    const data = JSON.parse(event.data);
  } else if (event.data instanceof Blob) {
    console.log('收到 Blob,大小:', event.data.size);
  } else if (event.data instanceof ArrayBuffer) {
    console.log('收到 ArrayBuffer,大小:', event.data.byteLength);
  }
};

关闭连接

javascript
// 关闭连接
ws.close();           // 默认关闭码 1000,无原因
ws.close(1000, '正常关闭');  // 自定义关闭码和原因

// 关闭码
ws.onclose = (event) => {
  console.log('关闭码:', event.code);
  console.log('关闭原因:', event.reason);
  console.log('是否干净关闭:', event.wasClean);
};

常用关闭码

关闭码名称说明
1000NORMAL_CLOSURE正常关闭
1001GOING_AWAY终端离开(如页面关闭)
1002PROTOCOL_ERROR协议错误
1003UNSUPPORTED_DATA不支持的数据类型
1005NO_STATUS_RECEIVED没有收到关闭码
1006ABNORMAL_CLOSURE异常关闭(无关闭帧)
1007INVALID_FRAME_PAYLOAD无效的数据负载
1008POLICY_VIOLATION策略违规
1009MESSAGE_TOO_BIG消息过大
1011INTERNAL_ERROR服务器内部错误
1012SERVICE_RESTART服务重启
1013TRY_AGAIN_LATER稍后重试
1014BAD_GATEWAY网关错误

详细说明

WebSocket 事件

事件触发时机说明
open连接建立成功可以开始发送数据
message收到服务器消息处理接收到的数据
error连接错误通信中出现错误
close连接关闭处理关闭逻辑
javascript
const ws = new WebSocket('wss://example.com/ws');

// 连接建立
ws.onopen = (event) => {
  console.log('连接已建立');
  ws.send('Hello');
};

// 收到消息
ws.onmessage = (event) => {
  console.log('收到:', event.data);
};

// 连接错误
ws.onerror = (event) => {
  console.error('WebSocket 错误:', event);
};

// 连接关闭
ws.onclose = (event) => {
  console.log(`连接关闭: code=${event.code}, reason=${event.reason}`);
};

WebSocket 属性

javascript
const ws = new WebSocket('wss://example.com/ws');

// readyState - 就绪状态
ws.readyState; // 0, 1, 2, 3

// bufferedAmount - 发送缓冲区中未发送的字节数
ws.send(largeData);
console.log(ws.bufferedAmount); // 查看缓冲区剩余数据量

// url - 连接 URL
ws.url; // 'wss://example.com/ws'

// protocol - 子协议
ws.protocol; // 如果服务器选择了 'json' 子协议

// extensions - 扩展
ws.extensions; // 服务器返回的扩展

// binaryType - 二进制数据类型
ws.binaryType = 'arraybuffer'; // 'arraybuffer' 或 'blob'
ws.binaryType = 'blob';

实战示例

WebSocket 连接测试工具

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WebSocket 测试工具</title>
  <style>
    body { font-family: -apple-system, sans-serif; padding: 20px; background: #f5f7fa; }

    .ws-card {
      max-width: 600px; margin: 0 auto; background: white;
      border-radius: 12px; padding: 24px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }

    h2 { text-align: center; color: #1a1a2e; }

    .connect-row {
      display: flex; gap: 10px; margin-bottom: 16px;
    }

    .connect-row input {
      flex: 1; padding: 10px; border: 2px solid #e0e0e0;
      border-radius: 8px; font-size: 14px;
    }

    .btn {
      padding: 10px 20px; border: none; border-radius: 8px;
      cursor: pointer; font-size: 14px; font-weight: 600; color: white;
    }

    .btn-connect { background: #4CAF50; }
    .btn-disconnect { background: #F44336; }
    .btn-send { background: #1a73e8; }
    .btn-clear { background: #9E9E9E; }

    .status-bar {
      display: flex; justify-content: space-between; align-items: center;
      padding: 10px 16px; background: #f0f0f0; border-radius: 8px;
      margin-bottom: 16px; font-size: 13px;
    }

    .status-dot {
      width: 10px; height: 10px; border-radius: 50%;
      display: inline-block; margin-right: 6px;
    }

    .status-dot.connected { background: #4CAF50; }
    .status-dot.disconnected { background: #ccc; }

    .message-log {
      height: 300px; overflow-y: auto; padding: 12px;
      background: #1a1a2e; border-radius: 8px;
      font-family: monospace; font-size: 13px;
    }

    .log-entry {
      padding: 3px 0;
    }

    .log-sent { color: #64b5f6; }
    .log-received { color: #81c784; }
    .log-system { color: #ffb74d; }
    .log-error { color: #ef5350; }

    .send-row {
      display: flex; gap: 10px; margin-top: 12px;
    }

    .send-row textarea {
      flex: 1; padding: 10px; border: 2px solid #e0e0e0;
      border-radius: 8px; font-size: 14px; resize: vertical;
      height: 60px; font-family: monospace;
    }
  </style>
</head>
<body>
  <div class="ws-card">
    <h2>WebSocket 测试工具</h2>

    <div class="connect-row">
      <input type="text" id="wsUrl" value="wss://echo.websocket.org" placeholder="ws:// 或 wss:// URL">
      <button class="btn btn-connect" id="connectBtn" onclick="connectWS()">连接</button>
      <button class="btn btn-disconnect" id="disconnectBtn" onclick="disconnectWS()" disabled>断开</button>
    </div>

    <div class="status-bar">
      <span><span class="status-dot disconnected" id="statusDot"></span><span id="statusText">未连接</span></span>
      <span id="readyState">readyState: --</span>
    </div>

    <div class="message-log" id="messageLog">
      <div class="log-entry log-system">等待连接...</div>
    </div>

    <div class="send-row">
      <textarea id="sendMsg" placeholder="输入要发送的消息"></textarea>
      <button class="btn btn-send" id="sendBtn" onclick="sendMessage()" disabled>发送</button>
      <button class="btn btn-clear" onclick="clearLog()">清空</button>
    </div>
  </div>

  <script>
    let ws = null;

    function addLog(text, type = 'system') {
      const log = document.getElementById('messageLog');
      const time = new Date().toLocaleTimeString();
      log.innerHTML += `<div class="log-entry log-${type}">[${time}] ${text}</div>`;
      log.scrollTop = log.scrollHeight;
    }

    function updateStatus(connected) {
      document.getElementById('statusDot').className = `status-dot ${connected ? 'connected' : 'disconnected'}`;
      document.getElementById('statusText').textContent = connected ? '已连接' : '未连接';
      document.getElementById('connectBtn').disabled = connected;
      document.getElementById('disconnectBtn').disabled = !connected;
      document.getElementById('sendBtn').disabled = !connected;
    }

    function updateReadyState() {
      if (ws) {
        const states = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
        document.getElementById('readyState').textContent = `readyState: ${states[ws.readyState]}`;
      }
    }

    function connectWS() {
      const url = document.getElementById('wsUrl').value.trim();
      if (!url) return;

      addLog(`正在连接 ${url}...`);
      updateReadyState();

      ws = new WebSocket(url);

      ws.onopen = () => {
        addLog('连接已建立', 'system');
        updateStatus(true);
        updateReadyState();
      };

      ws.onmessage = (event) => {
        addLog(`收到: ${event.data}`, 'received');
      };

      ws.onerror = (event) => {
        addLog('连接错误', 'error');
        updateReadyState();
      };

      ws.onclose = (event) => {
        addLog(`连接关闭 (code: ${event.code}, reason: ${event.reason || '无'})`, 'system');
        updateStatus(false);
        updateReadyState();
      };
    }

    function disconnectWS() {
      if (ws) {
        ws.close(1000, '用户主动断开');
        ws = null;
      }
    }

    function sendMessage() {
      if (ws && ws.readyState === WebSocket.OPEN) {
        const msg = document.getElementById('sendMsg').value;
        if (msg) {
          ws.send(msg);
          addLog(`发送: ${msg}`, 'sent');
          document.getElementById('sendMsg').value = '';
        }
      }
    }

    function clearLog() {
      document.getElementById('messageLog').innerHTML = '';
    }
  </script>
</body>
</html>

注意事项

  1. 必须使用 HTTPS/WSS:生产环境禁止使用未加密的 ws:// 协议
  2. 连接有开销:不要频繁创建和关闭连接
  3. 处理异常关闭close 事件的 code=1006 表示异常关闭
  4. 检查 bufferedAmount:发送大量数据前检查缓冲区
  5. 页面关闭时断开连接:在 beforeunload 事件中调用 ws.close()

最佳实践

  1. 连接成功后再发送数据,检查 readyState
  2. 使用 JSON 格式传递结构化消息
  3. 为消息添加类型字段,便于处理不同消息
  4. 设置二进制类型 binaryType 根据需求选择

下一节

继续学习:连接管理与重连

参考链接