Skip to content

WebSocket 实战

本节通过三个实战示例展示 WebSocket 的实际应用场景:聊天室、实时通知系统和二进制数据传输。每个示例包含前端实现和核心架构说明。

前置知识

阅读本节前,建议先了解:连接管理与重连

基础概念

WebSocket 实战场景

场景数据类型通信模式特点
聊天室JSON 文本多对多广播频繁短消息
实时通知JSON 文本服务器推送服务器主动推送
二进制传输ArrayBuffer双向大数据量传输

实战示例一:聊天室前端

消息协议

javascript
// 聊天室消息类型
const MsgType = {
  JOIN: 'join', MESSAGE: 'message', LEAVE: 'leave',
  WELCOME: 'welcome', USER_JOIN: 'user_join',
  USER_LEAVE: 'user_leave', CHAT_MESSAGE: 'chat_message',
  USER_LIST: 'user_list'
};

完整聊天室

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; }
    .chat-box {
      max-width: 600px; margin: 0 auto; background: white;
      border-radius: 12px; overflow: hidden;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    }
    .chat-header {
      padding: 16px 20px; background: #1a73e8; color: white;
      display: flex; justify-content: space-between; align-items: center;
    }
    .chat-header h2 { font-size: 18px; margin: 0; }
    .online { font-size: 13px; opacity: 0.8; }
    .messages {
      height: 400px; overflow-y: auto; padding: 16px;
      display: flex; flex-direction: column; gap: 10px;
    }
    .msg {
      max-width: 70%; padding: 10px 14px; border-radius: 12px;
      font-size: 14px; line-height: 1.5;
    }
    .msg.self {
      align-self: flex-end; background: #1a73e8; color: white;
      border-bottom-right-radius: 4px;
    }
    .msg.other {
      align-self: flex-start; background: #f0f0f0; color: #333;
      border-bottom-left-radius: 4px;
    }
    .msg .sender { font-size: 12px; font-weight: 600; margin-bottom: 2px; }
    .msg .time { font-size: 11px; opacity: 0.6; margin-top: 2px; }
    .system { text-align: center; font-size: 12px; color: #999; }
    .chat-input {
      display: flex; padding: 12px; border-top: 1px solid #eee; gap: 10px;
    }
    .chat-input input {
      flex: 1; padding: 10px 14px; border: 2px solid #e0e0e0;
      border-radius: 8px; font-size: 14px; outline: none;
    }
    .chat-input input:focus { border-color: #1a73e8; }
    .send-btn {
      padding: 10px 24px; background: #1a73e8; color: white;
      border: none; border-radius: 8px; cursor: pointer; font-weight: 600;
    }
    .login-overlay {
      position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0,0,0,0.5); display: flex;
      align-items: center; justify-content: center; z-index: 100;
    }
    .login-card {
      background: white; padding: 32px; border-radius: 12px;
      text-align: center; min-width: 300px;
    }
    .login-card input {
      width: 100%; padding: 10px; margin: 12px 0;
      border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px;
    }
    .login-card button {
      width: 100%; padding: 12px; background: #1a73e8; color: white;
      border: none; border-radius: 8px; font-size: 14px; font-weight: 600;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <div class="login-overlay" id="loginOverlay">
    <div class="login-card">
      <h2>加入聊天室</h2>
      <input type="text" id="username" placeholder="输入昵称" autofocus>
      <button onclick="joinChat()">进入聊天室</button>
    </div>
  </div>

  <div class="chat-box">
    <div class="chat-header">
      <h2>WebSocket 聊天室</h2>
      <span class="online" id="onlineCount">在线: 0</span>
    </div>
    <div class="messages" id="messages"></div>
    <div class="chat-input">
      <input type="text" id="chatInput" placeholder="输入消息..."
             onkeydown="if(event.key==='Enter')sendMessage()">
      <button class="send-btn" onclick="sendMessage()">发送</button>
    </div>
  </div>

  <script>
    let ws = null;
    let myName = '';

    function joinChat() {
      myName = document.getElementById('username').value.trim();
      if (!myName) return;

      document.getElementById('loginOverlay').style.display = 'none';

      // 连接 WebSocket(使用 echo 服务器模拟)
      ws = new WebSocket('wss://echo.websocket.org');

      ws.onopen = () => {
        addSystemMsg(`${myName} 加入了聊天室`);
        ws.send(JSON.stringify({ type: 'join', name: myName }));
      };

      ws.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data);
          handleServerMessage(data);
        } catch {
          // echo 服务器直接返回消息,模拟聊天
          addMessage(event.data, 'echo');
        }
      };

      ws.onclose = () => addSystemMsg('连接已断开');
      ws.onerror = () => addSystemMsg('连接错误');
    }

    function handleServerMessage(data) {
      switch (data.type) {
        case 'chat_message':
          addMessage(data.content, data.sender === myName ? 'self' : data.sender);
          break;
        case 'user_join':
          addSystemMsg(`${data.name} 加入了聊天室`);
          break;
        case 'user_leave':
          addSystemMsg(`${data.name} 离开了聊天室`);
          break;
        case 'user_list':
          document.getElementById('onlineCount').textContent = `在线: ${data.users.length}`;
          break;
      }
    }

    function sendMessage() {
      const input = document.getElementById('chatInput');
      const text = input.value.trim();
      if (!text || !ws) return;

      // 发送消息
      ws.send(JSON.stringify({ type: 'message', content: text }));
      addMessage(text, 'self');
      input.value = '';
    }

    function addMessage(text, sender) {
      const div = document.createElement('div');
      const isSelf = sender === 'self';
      div.className = `msg ${isSelf ? 'self' : 'other'}`;
      div.innerHTML = `
        <div class="sender">${isSelf ? myName : sender}</div>
        <div>${text}</div>
        <div class="time">${new Date().toLocaleTimeString()}</div>
      `;
      document.getElementById('messages').appendChild(div);
      scrollBottom();
    }

    function addSystemMsg(text) {
      const div = document.createElement('div');
      div.className = 'system';
      div.textContent = text;
      document.getElementById('messages').appendChild(div);
      scrollBottom();
    }

    function scrollBottom() {
      const el = document.getElementById('messages');
      el.scrollTop = el.scrollHeight;
    }

    window.addEventListener('beforeunload', () => {
      if (ws) ws.close(1001, '页面关闭');
    });
  </script>
</body>
</html>

实战示例二:实时通知系统

javascript
// 实时通知 WebSocket 客户端
class NotificationWS {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.handlers = {};
  }

  connect(token) {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      // 认证
      this.ws.send(JSON.stringify({ type: 'auth', token }));
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this._dispatch(data.type, data);
    };
  }

  on(type, handler) {
    if (!this.handlers[type]) this.handlers[type] = [];
    this.handlers[type].push(handler);
  }

  _dispatch(type, data) {
    (this.handlers[type] || []).forEach(h => h(data));

    // 调用浏览器通知
    if (type === 'notification' && Notification.permission === 'granted') {
      new Notification(data.title, { body: data.body, icon: data.icon });
    }
  }

  disconnect() {
    if (this.ws) this.ws.close();
  }
}

// 使用
const notiWS = new NotificationWS('wss://example.com/notifications');
notiWS.on('notification', (data) => {
  showNotification(data.title, data.body);
});
notiWS.on('badge', (data) => {
  updateBadge(data.count);
});

实战示例三:二进制数据传输

javascript
// WebSocket 二进制传输示例
const ws = new WebSocket('wss://example.com/binary');
ws.binaryType = 'arraybuffer';

ws.onopen = () => {
  // 发送 ArrayBuffer
  const buffer = new ArrayBuffer(1024);
  const view = new Float64Array(buffer);
  for (let i = 0; i < view.length; i++) {
    view[i] = Math.random() * 100;
  }
  ws.send(buffer);
  console.log('已发送 ArrayBuffer,大小:', buffer.byteLength);
};

ws.onmessage = (event) => {
  if (event.data instanceof ArrayBuffer) {
    const view = new Float64Array(event.data);
    console.log('收到二进制数据:', view.length, '个 Float64');
  }
};

// 发送 Blob
const blob = new Blob(['Hello WebSocket'], { type: 'text/plain' });
ws.send(blob);

注意事项

  1. 消息格式统一:使用 JSON 封装,包含 type 字段
  2. 认证机制:连接建立后先发送认证消息
  3. 心跳保活:长时间无消息时发送心跳帧
  4. 二进制设置:传输二进制前设置 binaryType
  5. 浏览器通知:结合 Notification API 实现桌面通知

最佳实践

  1. 设计清晰的消息协议,包含类型、数据和元信息
  2. 使用心跳检测连接状态,避免假死
  3. 为不同消息类型建立独立处理函数
  4. 大文件传输时分片发送,避免单帧过大
  5. 结合 bufferedAmount 实现流量控制

下一节

继续学习:Server-Sent Events 基础

参考链接