[{"data":1,"prerenderedAt":716},["ShallowReactive",2],{"standalone-realtime-applications-guide":3,"topic-realtime-applications-guide":715},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"category":10,"tags":11,"readTime":16,"createdAt":17,"image":18,"body":19,"_type":709,"_id":710,"_source":711,"_file":712,"_stem":713,"_extension":714},"/topics/realtime-applications-guide","topics",false,"","实时应用开发完全指南：WebSocket、消息队列与实时通信","深入学习 WebSocket、Server-Sent Events、消息队列等实时通信技术，构建高效的实时应用","实时应用",[12,13,14,15],"WebSocket","实时通信","消息队列","Socket.io",17,"2025-12-07","/images/topics/realtime-applications-guide.jpg",{"type":20,"children":21,"toc":671},"root",[22,31,37,41,47,53,63,68,252,255,261,267,278,284,293,299,308,314,323,326,332,338,347,353,361,364,370,376,385,391,400,403,409,415,424,430,441,444,450,456,465,471,480,486,495,498,504,510,519,525,534,537,543,589,592,597,602,666],{"type":23,"tag":24,"props":25,"children":27},"element","h2",{"id":26},"文章概述",[28],{"type":29,"value":30},"text","📖 文章概述",{"type":23,"tag":32,"props":33,"children":34},"p",{},[35],{"type":29,"value":36},"实时通信是现代应用的核心需求。本文讲解 WebSocket、Server-Sent Events、消息队列等技术，以及如何构建高效的实时应用。",{"type":23,"tag":38,"props":39,"children":40},"hr",{},[],{"type":23,"tag":24,"props":42,"children":44},{"id":43},"实时通信核心概念",[45],{"type":29,"value":46},"🎯 实时通信核心概念",{"type":23,"tag":48,"props":49,"children":51},"h3",{"id":50},"通信模型演进",[52],{"type":29,"value":50},{"type":23,"tag":54,"props":55,"children":57},"pre",{"code":56},"1. HTTP Polling（轮询）\n客户端 ──→ 服务器 ──→ 查询\n  ↓                      检查更新\n  等待 → 重复请求\n\n问题：浪费带宽、延迟高、服务器压力大\n\n2. HTTP Long Polling（长轮询）\n客户端 ──→ 服务器 ──→ 等待...\n           ↓\n           有数据时返回\n\n问题：仍然需要多个连接、复杂性高\n\n3. WebSocket（双向连接）\n客户端 ←──→ 服务器 ← 持久化连接\n     ↑                    ↓\n   发送消息            推送消息\n\n优点：低延迟、低开销、真正实时\n",[58],{"type":23,"tag":59,"props":60,"children":61},"code",{"__ignoreMap":7},[62],{"type":29,"value":56},{"type":23,"tag":48,"props":64,"children":66},{"id":65},"实时通信技术对比",[67],{"type":29,"value":65},{"type":23,"tag":69,"props":70,"children":71},"table",{},[72,106],{"type":23,"tag":73,"props":74,"children":75},"thead",{},[76],{"type":23,"tag":77,"props":78,"children":79},"tr",{},[80,86,91,96,101],{"type":23,"tag":81,"props":82,"children":83},"th",{},[84],{"type":29,"value":85},"技术",{"type":23,"tag":81,"props":87,"children":88},{},[89],{"type":29,"value":90},"延迟",{"type":23,"tag":81,"props":92,"children":93},{},[94],{"type":29,"value":95},"开销",{"type":23,"tag":81,"props":97,"children":98},{},[99],{"type":29,"value":100},"难度",{"type":23,"tag":81,"props":102,"children":103},{},[104],{"type":29,"value":105},"浏览器支持",{"type":23,"tag":107,"props":108,"children":109},"tbody",{},[110,142,170,198,225],{"type":23,"tag":77,"props":111,"children":112},{},[113,123,128,132,137],{"type":23,"tag":114,"props":115,"children":116},"td",{},[117],{"type":23,"tag":118,"props":119,"children":120},"strong",{},[121],{"type":29,"value":122},"Polling",{"type":23,"tag":114,"props":124,"children":125},{},[126],{"type":29,"value":127},"高",{"type":23,"tag":114,"props":129,"children":130},{},[131],{"type":29,"value":127},{"type":23,"tag":114,"props":133,"children":134},{},[135],{"type":29,"value":136},"低",{"type":23,"tag":114,"props":138,"children":139},{},[140],{"type":29,"value":141},"全部",{"type":23,"tag":77,"props":143,"children":144},{},[145,153,158,162,166],{"type":23,"tag":114,"props":146,"children":147},{},[148],{"type":23,"tag":118,"props":149,"children":150},{},[151],{"type":29,"value":152},"Long Polling",{"type":23,"tag":114,"props":154,"children":155},{},[156],{"type":29,"value":157},"中",{"type":23,"tag":114,"props":159,"children":160},{},[161],{"type":29,"value":157},{"type":23,"tag":114,"props":163,"children":164},{},[165],{"type":29,"value":157},{"type":23,"tag":114,"props":167,"children":168},{},[169],{"type":29,"value":141},{"type":23,"tag":77,"props":171,"children":172},{},[173,181,185,189,193],{"type":23,"tag":114,"props":174,"children":175},{},[176],{"type":23,"tag":118,"props":177,"children":178},{},[179],{"type":29,"value":180},"Server-Sent Events",{"type":23,"tag":114,"props":182,"children":183},{},[184],{"type":29,"value":136},{"type":23,"tag":114,"props":186,"children":187},{},[188],{"type":29,"value":136},{"type":23,"tag":114,"props":190,"children":191},{},[192],{"type":29,"value":136},{"type":23,"tag":114,"props":194,"children":195},{},[196],{"type":29,"value":197},"99%",{"type":23,"tag":77,"props":199,"children":200},{},[201,208,213,217,221],{"type":23,"tag":114,"props":202,"children":203},{},[204],{"type":23,"tag":118,"props":205,"children":206},{},[207],{"type":29,"value":12},{"type":23,"tag":114,"props":209,"children":210},{},[211],{"type":29,"value":212},"极低",{"type":23,"tag":114,"props":214,"children":215},{},[216],{"type":29,"value":212},{"type":23,"tag":114,"props":218,"children":219},{},[220],{"type":29,"value":157},{"type":23,"tag":114,"props":222,"children":223},{},[224],{"type":29,"value":197},{"type":23,"tag":77,"props":226,"children":227},{},[228,235,239,243,247],{"type":23,"tag":114,"props":229,"children":230},{},[231],{"type":23,"tag":118,"props":232,"children":233},{},[234],{"type":29,"value":14},{"type":23,"tag":114,"props":236,"children":237},{},[238],{"type":29,"value":157},{"type":23,"tag":114,"props":240,"children":241},{},[242],{"type":29,"value":136},{"type":23,"tag":114,"props":244,"children":245},{},[246],{"type":29,"value":127},{"type":23,"tag":114,"props":248,"children":249},{},[250],{"type":29,"value":251},"N/A",{"type":23,"tag":38,"props":253,"children":254},{},[],{"type":23,"tag":24,"props":256,"children":258},{"id":257},"websocket-完全指南",[259],{"type":29,"value":260},"🚀 WebSocket 完全指南",{"type":23,"tag":48,"props":262,"children":264},{"id":263},"_1-websocket-基础",[265],{"type":29,"value":266},"1. WebSocket 基础",{"type":23,"tag":54,"props":268,"children":273},{"code":269,"language":270,"meta":7,"className":271},"// 客户端 WebSocket\nconst socket = new WebSocket('ws://localhost:8080')\n\n// 连接打开\nsocket.addEventListener('open', () => {\n  console.log('WebSocket 连接已打开')\n  socket.send('Hello Server!')\n})\n\n// 接收消息\nsocket.addEventListener('message', (event) => {\n  console.log('收到消息:', event.data)\n})\n\n// 连接关闭\nsocket.addEventListener('close', () => {\n  console.log('WebSocket 连接已关闭')\n  // 重新连接逻辑\n  setTimeout(() => {\n    reconnectWebSocket()\n  }, 3000)\n})\n\n// 错误处理\nsocket.addEventListener('error', (event) => {\n  console.error('WebSocket 错误:', event)\n})\n","javascript",[272],"language-javascript",[274],{"type":23,"tag":59,"props":275,"children":276},{"__ignoreMap":7},[277],{"type":29,"value":269},{"type":23,"tag":48,"props":279,"children":281},{"id":280},"_2-nodejs-websocket-服务器",[282],{"type":29,"value":283},"2. Node.js WebSocket 服务器",{"type":23,"tag":54,"props":285,"children":288},{"code":286,"language":270,"meta":7,"className":287},"// 使用 ws 库（轻量级）\nimport WebSocket from 'ws'\n\nconst wss = new WebSocket.Server({ port: 8080 })\n\nwss.on('connection', (ws) => {\n  console.log('客户端已连接')\n  \n  // 发送初始消息\n  ws.send(JSON.stringify({\n    type: 'welcome',\n    message: 'Welcome to WebSocket server'\n  }))\n  \n  // 接收消息\n  ws.on('message', (data) => {\n    const message = JSON.parse(data)\n    console.log('收到消息:', message)\n    \n    // 广播给所有客户端\n    wss.clients.forEach((client) => {\n      if (client.readyState === WebSocket.OPEN) {\n        client.send(JSON.stringify({\n          type: 'broadcast',\n          data: message\n        }))\n      }\n    })\n  })\n  \n  // 客户端断开连接\n  ws.on('close', () => {\n    console.log('客户端已断开连接')\n  })\n  \n  // 错误处理\n  ws.on('error', (error) => {\n    console.error('WebSocket 错误:', error)\n  })\n})\n",[272],[289],{"type":23,"tag":59,"props":290,"children":291},{"__ignoreMap":7},[292],{"type":29,"value":286},{"type":23,"tag":48,"props":294,"children":296},{"id":295},"_3-socketio生产级方案",[297],{"type":29,"value":298},"3. Socket.io（生产级方案）",{"type":23,"tag":54,"props":300,"children":303},{"code":301,"language":270,"meta":7,"className":302},"// 服务器端\nimport io from 'socket.io'\n\nconst server = io(3000, {\n  cors: { origin: '*' }\n})\n\nserver.on('connection', (socket) => {\n  console.log('客户端已连接:', socket.id)\n  \n  // 发送消息给该客户端\n  socket.emit('welcome', {\n    message: 'Welcome!',\n    socketId: socket.id\n  })\n  \n  // 接收消息\n  socket.on('chat message', (msg) => {\n    console.log('消息:', msg)\n    \n    // 发送给所有客户端\n    server.emit('chat message', {\n      from: socket.id,\n      message: msg,\n      timestamp: Date.now()\n    })\n  })\n  \n  // 房间管理\n  socket.on('join room', (roomId) => {\n    socket.join(roomId)\n    \n    // 通知房间内其他成员\n    socket.to(roomId).emit('user joined', {\n      userId: socket.id,\n      message: '新用户加入了房间'\n    })\n  })\n  \n  // 发送给指定房间\n  socket.on('room message', (roomId, msg) => {\n    server.to(roomId).emit('room message', {\n      from: socket.id,\n      message: msg\n    })\n  })\n  \n  // 断开连接\n  socket.on('disconnect', () => {\n    console.log('客户端已断开连接:', socket.id)\n  })\n})\n",[272],[304],{"type":23,"tag":59,"props":305,"children":306},{"__ignoreMap":7},[307],{"type":29,"value":301},{"type":23,"tag":48,"props":309,"children":311},{"id":310},"_4-socketio-客户端",[312],{"type":29,"value":313},"4. Socket.io 客户端",{"type":23,"tag":54,"props":315,"children":318},{"code":316,"language":270,"meta":7,"className":317},"// 浏览器客户端\nimport io from 'socket.io-client'\n\nconst socket = io('http://localhost:3000', {\n  reconnection: true,\n  reconnectionDelay: 1000,\n  reconnectionDelayMax: 5000,\n  reconnectionAttempts: 5\n})\n\n// 监听连接\nsocket.on('connect', () => {\n  console.log('已连接到服务器')\n})\n\n// 接收欢迎消息\nsocket.on('welcome', (data) => {\n  console.log('欢迎消息:', data)\n})\n\n// 接收聊天消息\nsocket.on('chat message', (data) => {\n  console.log('新消息:', data)\n  addMessageToUI(data)\n})\n\n// 加入房间\nfunction joinRoom(roomId) {\n  socket.emit('join room', roomId)\n}\n\n// 发送消息\nfunction sendMessage(message) {\n  socket.emit('chat message', message)\n}\n\n// 发送房间消息\nfunction sendRoomMessage(roomId, message) {\n  socket.emit('room message', roomId, message)\n}\n\n// 监听连接失败\nsocket.on('connect_error', (error) => {\n  console.error('连接错误:', error)\n})\n\n// 监听断开连接\nsocket.on('disconnect', () => {\n  console.log('连接已断开')\n})\n",[272],[319],{"type":23,"tag":59,"props":320,"children":321},{"__ignoreMap":7},[322],{"type":29,"value":316},{"type":23,"tag":38,"props":324,"children":325},{},[],{"type":23,"tag":24,"props":327,"children":329},{"id":328},"server-sent-events-sse",[330],{"type":29,"value":331},"📡 Server-Sent Events (SSE)",{"type":23,"tag":48,"props":333,"children":335},{"id":334},"_5-sse-实现",[336],{"type":29,"value":337},"5. SSE 实现",{"type":23,"tag":54,"props":339,"children":342},{"code":340,"language":270,"meta":7,"className":341},"// 服务器端（Express）\napp.get('/events', (req, res) => {\n  // 设置 SSE 响应头\n  res.setHeader('Content-Type', 'text/event-stream')\n  res.setHeader('Cache-Control', 'no-cache')\n  res.setHeader('Connection', 'keep-alive')\n  \n  // 发送初始消息\n  res.write('data: Connected to server\\n\\n')\n  \n  // 定期发送消息\n  const interval = setInterval(() => {\n    const message = {\n      timestamp: new Date(),\n      data: Math.random()\n    }\n    res.write(`data: ${JSON.stringify(message)}\\n\\n`)\n  }, 1000)\n  \n  // 客户端断开时清理\n  req.on('close', () => {\n    clearInterval(interval)\n    res.end()\n  })\n})\n\n// 客户端\nconst eventSource = new EventSource('/events')\n\n// 接收消息\neventSource.addEventListener('message', (event) => {\n  const data = JSON.parse(event.data)\n  console.log('收到数据:', data)\n})\n\n// 错误处理\neventSource.addEventListener('error', (error) => {\n  console.error('SSE 错误:', error)\n  eventSource.close()\n})\n\n// 关闭连接\nfunction closeEventSource() {\n  eventSource.close()\n}\n",[272],[343],{"type":23,"tag":59,"props":344,"children":345},{"__ignoreMap":7},[346],{"type":29,"value":340},{"type":23,"tag":48,"props":348,"children":350},{"id":349},"sse-vs-websocket",[351],{"type":29,"value":352},"SSE vs WebSocket",{"type":23,"tag":54,"props":354,"children":356},{"code":355},"SSE 优点：\n- 使用标准 HTTP\n- 自动重连\n- 简单易用\n\nSSE 缺点：\n- 单向通信（服务器→客户端）\n- 不适合高频双向通信\n\nWebSocket 优点：\n- 双向通信\n- 低延迟\n- 高效\n\nWebSocket 缺点：\n- 需要特殊库\n- 需要处理连接管理\n",[357],{"type":23,"tag":59,"props":358,"children":359},{"__ignoreMap":7},[360],{"type":29,"value":355},{"type":23,"tag":38,"props":362,"children":363},{},[],{"type":23,"tag":24,"props":365,"children":367},{"id":366},"消息队列实时应用",[368],{"type":29,"value":369},"🔄 消息队列实时应用",{"type":23,"tag":48,"props":371,"children":373},{"id":372},"_6-redis-pubsub",[374],{"type":29,"value":375},"6. Redis Pub/Sub",{"type":23,"tag":54,"props":377,"children":380},{"code":378,"language":270,"meta":7,"className":379},"// 发布者\nimport redis from 'redis'\n\nconst publisher = redis.createClient()\n\n// 发布消息\nfunction publishMessage(channel, message) {\n  publisher.publish(channel, JSON.stringify(message))\n}\n\n// 订阅者\nconst subscriber = redis.createClient()\n\nsubscriber.subscribe('notifications', (message) => {\n  const data = JSON.parse(message)\n  console.log('收到通知:', data)\n  \n  // 将消息推送给所有连接的客户端\n  broadcastToClients(data)\n})\n\n// 在 WebSocket 服务器中使用\nserver.on('connection', (socket) => {\n  // 订阅某个频道\n  socket.on('subscribe', (channel) => {\n    const sub = redis.createClient()\n    sub.subscribe(channel, (message) => {\n      socket.emit('message', JSON.parse(message))\n    })\n    \n    // 保存订阅以便断开时清理\n    socket.subscriptions = socket.subscriptions || []\n    socket.subscriptions.push(sub)\n  })\n  \n  // 断开时关闭订阅\n  socket.on('disconnect', () => {\n    if (socket.subscriptions) {\n      socket.subscriptions.forEach(sub => sub.unsubscribe())\n    }\n  })\n})\n",[272],[381],{"type":23,"tag":59,"props":382,"children":383},{"__ignoreMap":7},[384],{"type":29,"value":378},{"type":23,"tag":48,"props":386,"children":388},{"id":387},"_7-rabbitmq-消息队列",[389],{"type":29,"value":390},"7. RabbitMQ 消息队列",{"type":23,"tag":54,"props":392,"children":395},{"code":393,"language":270,"meta":7,"className":394},"// 使用 amqplib（RabbitMQ 客户端）\nimport amqp from 'amqplib'\n\n// 生产者\nasync function sendMessage(queue, message) {\n  const connection = await amqp.connect('amqp://localhost')\n  const channel = await connection.createChannel()\n  \n  // 声明队列\n  await channel.assertQueue(queue, { durable: true })\n  \n  // 发送消息\n  channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), {\n    persistent: true  // 消息持久化\n  })\n  \n  console.log('消息已发送:', message)\n}\n\n// 消费者\nasync function consumeMessages(queue) {\n  const connection = await amqp.connect('amqp://localhost')\n  const channel = await connection.createChannel()\n  \n  // 声明队列\n  await channel.assertQueue(queue, { durable: true })\n  \n  // 设置并发数\n  channel.prefetch(1)\n  \n  // 消费消息\n  channel.consume(queue, (msg) => {\n    if (msg) {\n      const message = JSON.parse(msg.content.toString())\n      console.log('处理消息:', message)\n      \n      // 处理消息\n      processMessage(message).then(() => {\n        // 确认消息\n        channel.ack(msg)\n      })\n    }\n  })\n}\n\n// 在 Express 中整合\napp.post('/api/messages', async (req, res) => {\n  try {\n    await sendMessage('notifications', req.body)\n    res.json({ success: true })\n  } catch (error) {\n    res.status(500).json({ error: error.message })\n  }\n})\n",[272],[396],{"type":23,"tag":59,"props":397,"children":398},{"__ignoreMap":7},[399],{"type":29,"value":393},{"type":23,"tag":38,"props":401,"children":402},{},[],{"type":23,"tag":24,"props":404,"children":406},{"id":405},"实战实时聊天应用",[407],{"type":29,"value":408},"🎯 实战：实时聊天应用",{"type":23,"tag":48,"props":410,"children":412},{"id":411},"_8-完整实时聊天系统",[413],{"type":29,"value":414},"8. 完整实时聊天系统",{"type":23,"tag":54,"props":416,"children":419},{"code":417,"language":270,"meta":7,"className":418},"// server.js\nimport express from 'express'\nimport io from 'socket.io'\nimport http from 'http'\n\nconst app = express()\nconst server = http.createServer(app)\nconst socket_io = io(server, {\n  cors: { origin: '*' }\n})\n\n// 在内存中存储用户（实际应用应使用数据库）\nconst users = new Map()\nconst rooms = new Map()\n\nsocket_io.on('connection', (socket) => {\n  console.log('用户已连接:', socket.id)\n  \n  // 用户加入\n  socket.on('join', (data) => {\n    const { username, roomId } = data\n    \n    // 保存用户信息\n    users.set(socket.id, {\n      id: socket.id,\n      username,\n      roomId\n    })\n    \n    // 加入房间\n    socket.join(roomId)\n    \n    // 通知房间内的其他用户\n    socket.to(roomId).emit('user:joined', {\n      username,\n      userId: socket.id,\n      timestamp: Date.now()\n    })\n    \n    // 发送房间内的用户列表\n    const roomUsers = Array.from(users.values())\n      .filter(u => u.roomId === roomId)\n    \n    socket.emit('users:list', roomUsers)\n  })\n  \n  // 接收聊天消息\n  socket.on('message:send', (data) => {\n    const user = users.get(socket.id)\n    if (!user) return\n    \n    const message = {\n      id: Math.random().toString(36),\n      userId: socket.id,\n      username: user.username,\n      content: data.content,\n      timestamp: Date.now()\n    }\n    \n    // 广播给房间内的所有用户\n    socket_io.to(user.roomId).emit('message:new', message)\n  })\n  \n  // 输入状态\n  socket.on('typing:start', () => {\n    const user = users.get(socket.id)\n    if (!user) return\n    \n    socket.to(user.roomId).emit('user:typing', {\n      userId: socket.id,\n      username: user.username\n    })\n  })\n  \n  socket.on('typing:stop', () => {\n    const user = users.get(socket.id)\n    if (!user) return\n    \n    socket.to(user.roomId).emit('user:stopped-typing', {\n      userId: socket.id\n    })\n  })\n  \n  // 用户断开连接\n  socket.on('disconnect', () => {\n    const user = users.get(socket.id)\n    if (user) {\n      users.delete(socket.id)\n      \n      socket_io.to(user.roomId).emit('user:left', {\n        username: user.username,\n        userId: socket.id\n      })\n    }\n  })\n})\n\nserver.listen(3000, () => {\n  console.log('聊天服务器运行在 http://localhost:3000')\n})\n",[272],[420],{"type":23,"tag":59,"props":421,"children":422},{"__ignoreMap":7},[423],{"type":29,"value":417},{"type":23,"tag":48,"props":425,"children":427},{"id":426},"_9-前端-vue-聊天组件",[428],{"type":29,"value":429},"9. 前端 Vue 聊天组件",{"type":23,"tag":54,"props":431,"children":436},{"code":432,"language":433,"meta":7,"className":434},"\u003Ctemplate>\n  \u003Cdiv class=\"chat-container\">\n    \u003C!-- 用户列表 -->\n    \u003Caside class=\"sidebar\">\n      \u003Cdiv class=\"room-info\">\n        \u003Ch2>{{ roomId }}\u003C/h2>\n        \u003Cp>用户数: {{ users.length }}\u003C/p>\n      \u003C/div>\n      \n      \u003Cdiv class=\"users-list\">\n        \u003Cdiv\n          v-for=\"user in users\"\n          :key=\"user.id\"\n          class=\"user-item\"\n          :class=\"{ typing: typingUsers.has(user.id) }\"\n        >\n          {{ user.username }}\n          \u003Cspan v-if=\"typingUsers.has(user.id)\" class=\"typing-indicator\">\n            输入中...\n          \u003C/span>\n        \u003C/div>\n      \u003C/div>\n    \u003C/aside>\n    \n    \u003C!-- 聊天区域 -->\n    \u003Cdiv class=\"chat-area\">\n      \u003C!-- 消息列表 -->\n      \u003Cdiv class=\"messages\" ref=\"messagesContainer\">\n        \u003Cdiv\n          v-for=\"message in messages\"\n          :key=\"message.id\"\n          class=\"message\"\n          :class=\"{ 'own-message': message.userId === currentUserId }\"\n        >\n          \u003Cdiv class=\"message-header\">\n            \u003Cstrong>{{ message.username }}\u003C/strong>\n            \u003Cspan class=\"timestamp\">\n              {{ formatTime(message.timestamp) }}\n            \u003C/span>\n          \u003C/div>\n          \u003Cdiv class=\"message-content\">{{ message.content }}\u003C/div>\n        \u003C/div>\n      \u003C/div>\n      \n      \u003C!-- 输入区域 -->\n      \u003Cdiv class=\"input-area\">\n        \u003Cinput\n          v-model=\"inputMessage\"\n          type=\"text\"\n          placeholder=\"输入消息...\"\n          @keyup.enter=\"sendMessage\"\n          @input=\"handleTyping\"\n        />\n        \u003Cbutton @click=\"sendMessage\">发送\u003C/button>\n      \u003C/div>\n    \u003C/div>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport { io } from 'socket.io-client'\n\ninterface User {\n  id: string\n  username: string\n  roomId: string\n}\n\ninterface Message {\n  id: string\n  userId: string\n  username: string\n  content: string\n  timestamp: number\n}\n\nconst props = defineProps({\n  username: String,\n  roomId: String\n})\n\nconst socket = io('http://localhost:3000')\nconst messages = reactive\u003CMessage[]>([])\nconst users = reactive\u003CUser[]>([])\nconst typingUsers = ref(new Set\u003Cstring>())\nconst inputMessage = ref('')\nconst currentUserId = ref('')\nconst messagesContainer = ref\u003CHTMLElement>()\nlet typingTimeout: NodeJS.Timeout\n\n// 加入房间\nsocket.on('connect', () => {\n  currentUserId.value = socket.id!\n  socket.emit('join', {\n    username: props.username,\n    roomId: props.roomId\n  })\n})\n\n// 接收用户列表\nsocket.on('users:list', (userList) => {\n  users.splice(0, users.length, ...userList)\n})\n\n// 新用户加入\nsocket.on('user:joined', (user) => {\n  console.log(`${user.username} 加入了房间`)\n})\n\n// 新消息\nsocket.on('message:new', (message: Message) => {\n  messages.push(message)\n  \n  // 自动滚动到最新消息\n  nextTick(() => {\n    if (messagesContainer.value) {\n      messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight\n    }\n  })\n})\n\n// 用户输入\nsocket.on('user:typing', (data) => {\n  typingUsers.value.add(data.userId)\n})\n\nsocket.on('user:stopped-typing', (data) => {\n  typingUsers.value.delete(data.userId)\n})\n\n// 用户离开\nsocket.on('user:left', (user) => {\n  console.log(`${user.username} 离开了房间`)\n  users.splice(users.findIndex(u => u.id === user.userId), 1)\n})\n\n// 发送消息\nconst sendMessage = () => {\n  if (!inputMessage.value.trim()) return\n  \n  socket.emit('message:send', {\n    content: inputMessage.value\n  })\n  \n  inputMessage.value = ''\n  socket.emit('typing:stop')\n}\n\n// 输入状态处理\nconst handleTyping = () => {\n  clearTimeout(typingTimeout)\n  \n  socket.emit('typing:start')\n  \n  typingTimeout = setTimeout(() => {\n    socket.emit('typing:stop')\n  }, 2000)\n}\n\n// 时间格式化\nconst formatTime = (timestamp: number) => {\n  return new Date(timestamp).toLocaleTimeString()\n}\n\n// 清理\nonUnmounted(() => {\n  socket.disconnect()\n})\n\u003C/script>\n\n\u003Cstyle scoped>\n.chat-container {\n  display: flex;\n  height: 100vh;\n  gap: 1px;\n  background: #f0f0f0;\n}\n\n.sidebar {\n  width: 250px;\n  background: white;\n  border-right: 1px solid #ddd;\n  overflow-y: auto;\n}\n\n.room-info {\n  padding: 15px;\n  border-bottom: 1px solid #ddd;\n}\n\n.users-list {\n  padding: 10px;\n}\n\n.user-item {\n  padding: 8px;\n  margin: 5px 0;\n  background: #f5f5f5;\n  border-radius: 4px;\n  transition: background 0.3s;\n}\n\n.user-item.typing {\n  background: #e3f2fd;\n}\n\n.typing-indicator {\n  font-size: 0.8em;\n  color: #999;\n  margin-left: 10px;\n}\n\n.chat-area {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  background: white;\n}\n\n.messages {\n  flex: 1;\n  overflow-y: auto;\n  padding: 15px;\n}\n\n.message {\n  margin: 10px 0;\n  padding: 10px;\n  background: #f5f5f5;\n  border-radius: 8px;\n  max-width: 70%;\n}\n\n.message.own-message {\n  margin-left: auto;\n  background: #e3f2fd;\n}\n\n.message-header {\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: 5px;\n  font-size: 0.9em;\n}\n\n.timestamp {\n  color: #999;\n}\n\n.message-content {\n  word-wrap: break-word;\n}\n\n.input-area {\n  display: flex;\n  padding: 15px;\n  gap: 10px;\n  border-top: 1px solid #ddd;\n}\n\n.input-area input {\n  flex: 1;\n  padding: 8px;\n  border: 1px solid #ddd;\n  border-radius: 4px;\n}\n\n.input-area button {\n  padding: 8px 20px;\n  background: #007bff;\n  color: white;\n  border: none;\n  border-radius: 4px;\n  cursor: pointer;\n}\n\n.input-area button:hover {\n  background: #0056b3;\n}\n\u003C/style>\n","vue",[435],"language-vue",[437],{"type":23,"tag":59,"props":438,"children":439},{"__ignoreMap":7},[440],{"type":29,"value":432},{"type":23,"tag":38,"props":442,"children":443},{},[],{"type":23,"tag":24,"props":445,"children":447},{"id":446},"常见问题解决",[448],{"type":29,"value":449},"🐛 常见问题解决",{"type":23,"tag":48,"props":451,"children":453},{"id":452},"问题-1连接断开重连",[454],{"type":29,"value":455},"问题 1：连接断开重连",{"type":23,"tag":54,"props":457,"children":460},{"code":458,"language":270,"meta":7,"className":459},"const socket = io('http://localhost:3000', {\n  reconnection: true,\n  reconnectionDelay: 1000,\n  reconnectionDelayMax: 5000,\n  reconnectionAttempts: 5,\n  transports: ['websocket', 'polling']  // 降级支持\n})\n\nsocket.on('disconnect', () => {\n  console.log('连接已断开，将自动重连...')\n})\n\nsocket.on('connect', () => {\n  console.log('连接已恢复')\n  // 重新加载数据\n  reloadData()\n})\n",[272],[461],{"type":23,"tag":59,"props":462,"children":463},{"__ignoreMap":7},[464],{"type":29,"value":458},{"type":23,"tag":48,"props":466,"children":468},{"id":467},"问题-2消息丢失",[469],{"type":29,"value":470},"问题 2：消息丢失",{"type":23,"tag":54,"props":472,"children":475},{"code":473,"language":270,"meta":7,"className":474},"// 使用消息队列确保可靠传递\nconst queue: Message[] = []\n\nsocket.on('disconnect', () => {\n  // 保存未发送的消息\n  localStorage.setItem('pendingMessages', JSON.stringify(queue))\n})\n\nsocket.on('connect', () => {\n  // 恢复并重发未发送的消息\n  const pending = JSON.parse(localStorage.getItem('pendingMessages') || '[]')\n  pending.forEach(msg => {\n    socket.emit('message:send', msg)\n  })\n  localStorage.removeItem('pendingMessages')\n})\n",[272],[476],{"type":23,"tag":59,"props":477,"children":478},{"__ignoreMap":7},[479],{"type":29,"value":473},{"type":23,"tag":48,"props":481,"children":483},{"id":482},"问题-3扩展性问题",[484],{"type":29,"value":485},"问题 3：扩展性问题",{"type":23,"tag":54,"props":487,"children":490},{"code":488,"language":270,"meta":7,"className":489},"// 使用消息队列（Redis Pub/Sub）实现多服务器支持\n\n// 服务器 1 和服务器 2 都订阅相同频道\nconst subscriber = redis.createClient()\nsubscriber.subscribe('chat-messages')\n\nsocket_io.on('connection', (socket) => {\n  socket.on('message:send', (message) => {\n    // 发布到 Redis，所有服务器都能接收\n    publisher.publish('chat-messages', JSON.stringify(message))\n  })\n})\n\n// 接收来自 Redis 的消息\nsubscriber.on('message', (channel, message) => {\n  const data = JSON.parse(message)\n  // 广播给当前服务器的所有客户端\n  socket_io.emit('message:new', data)\n})\n",[272],[491],{"type":23,"tag":59,"props":492,"children":493},{"__ignoreMap":7},[494],{"type":29,"value":488},{"type":23,"tag":38,"props":496,"children":497},{},[],{"type":23,"tag":24,"props":499,"children":501},{"id":500},"最佳实践",[502],{"type":29,"value":503},"🎓 最佳实践",{"type":23,"tag":48,"props":505,"children":507},{"id":506},"do",[508],{"type":29,"value":509},"DO ✅",{"type":23,"tag":54,"props":511,"children":514},{"code":512,"language":270,"meta":7,"className":513},"// 1. 实现自动重连\nconst socket = io(url, { reconnection: true })\n\n// 2. 使用心跳检测连接\nsetInterval(() => {\n  socket.emit('ping')\n}, 30000)\n\n// 3. 区分消息优先级\nsocket.emit('message:send', {\n  content: msg,\n  priority: 'high'\n})\n\n// 4. 限制消息频率\nconst throttle = (fn, delay) => {\n  let timeout\n  return () => {\n    clearTimeout(timeout)\n    timeout = setTimeout(fn, delay)\n  }\n}\n\nsocket.on('input', throttle(() => {\n  socket.emit('typing')\n}, 500))\n\n// 5. 使用认证\nsocket.on('connect', () => {\n  socket.emit('authenticate', { token })\n})\n",[272],[515],{"type":23,"tag":59,"props":516,"children":517},{"__ignoreMap":7},[518],{"type":29,"value":512},{"type":23,"tag":48,"props":520,"children":522},{"id":521},"dont",[523],{"type":29,"value":524},"DON'T ❌",{"type":23,"tag":54,"props":526,"children":529},{"code":527,"language":270,"meta":7,"className":528},"// 1. 不要忽视错误处理\n// socket.emit('message')  // ❌ 无错误处理\n\nsocket.emit('message', {}, (error) => {\n  if (error) console.error(error)\n})\n\n// 2. 不要频繁创建新连接\n// new WebSocket(url)  // 每次都创建新连接\n\nconst socket = io(url)  // 单一实例\n\n// 3. 不要在 disconnect 时立即清理\n// ❌ socket.off('message')\n\n// ✅ 等待重连失败后再清理\n\n// 4. 不要发送过大的消息\n// socket.emit('message', largeObject)\n\n// ✅ 分块发送或压缩\n",[272],[530],{"type":23,"tag":59,"props":531,"children":532},{"__ignoreMap":7},[533],{"type":29,"value":527},{"type":23,"tag":38,"props":535,"children":536},{},[],{"type":23,"tag":24,"props":538,"children":540},{"id":539},"扩展资源",[541],{"type":29,"value":542},"📚 扩展资源",{"type":23,"tag":544,"props":545,"children":546},"ul",{},[547,560,570,579],{"type":23,"tag":548,"props":549,"children":550},"li",{},[551],{"type":23,"tag":552,"props":553,"children":557},"a",{"href":554,"rel":555},"https://socket.io/docs/",[556],"nofollow",[558],{"type":29,"value":559},"Socket.io 官方文档",{"type":23,"tag":548,"props":561,"children":562},{},[563],{"type":23,"tag":552,"props":564,"children":567},{"href":565,"rel":566},"https://developer.mozilla.org/en-US/docs/Web/API/WebSocket",[556],[568],{"type":29,"value":569},"WebSocket API 文档",{"type":23,"tag":548,"props":571,"children":572},{},[573],{"type":23,"tag":552,"props":574,"children":577},{"href":575,"rel":576},"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events",[556],[578],{"type":29,"value":180},{"type":23,"tag":548,"props":580,"children":581},{},[582],{"type":23,"tag":552,"props":583,"children":586},{"href":584,"rel":585},"https://www.ably.io/topic/real-time",[556],[587],{"type":29,"value":588},"实时应用架构",{"type":23,"tag":38,"props":590,"children":591},{},[],{"type":23,"tag":24,"props":593,"children":595},{"id":594},"总结",[596],{"type":29,"value":594},{"type":23,"tag":32,"props":598,"children":599},{},[600],{"type":29,"value":601},"实时应用开发的关键要素：",{"type":23,"tag":603,"props":604,"children":605},"ol",{},[606,616,626,636,646,656],{"type":23,"tag":548,"props":607,"children":608},{},[609,614],{"type":23,"tag":118,"props":610,"children":611},{},[612],{"type":29,"value":613},"选择正确的技术",{"type":29,"value":615},"：WebSocket（双向）、SSE（单向）、消息队列（可靠性）",{"type":23,"tag":548,"props":617,"children":618},{},[619,624],{"type":23,"tag":118,"props":620,"children":621},{},[622],{"type":29,"value":623},"连接管理",{"type":29,"value":625},"：自动重连、心跳检测、状态恢复",{"type":23,"tag":548,"props":627,"children":628},{},[629,634],{"type":23,"tag":118,"props":630,"children":631},{},[632],{"type":29,"value":633},"消息处理",{"type":29,"value":635},"：去重、排序、优先级、限流",{"type":23,"tag":548,"props":637,"children":638},{},[639,644],{"type":23,"tag":118,"props":640,"children":641},{},[642],{"type":29,"value":643},"扩展性",{"type":29,"value":645},"：使用消息队列支持多服务器",{"type":23,"tag":548,"props":647,"children":648},{},[649,654],{"type":23,"tag":118,"props":650,"children":651},{},[652],{"type":29,"value":653},"性能优化",{"type":29,"value":655},"：压缩、分块、缓存",{"type":23,"tag":548,"props":657,"children":658},{},[659,664],{"type":23,"tag":118,"props":660,"children":661},{},[662],{"type":29,"value":663},"用户体验",{"type":29,"value":665},"：离线提示、输入状态、错误恢复",{"type":23,"tag":32,"props":667,"children":668},{},[669],{"type":29,"value":670},"掌握实时通信技术，构建响应式、沉浸式的应用！",{"title":7,"searchDepth":672,"depth":672,"links":673},3,[674,676,680,686,690,694,698,703,707,708],{"id":26,"depth":675,"text":30},2,{"id":43,"depth":675,"text":46,"children":677},[678,679],{"id":50,"depth":672,"text":50},{"id":65,"depth":672,"text":65},{"id":257,"depth":675,"text":260,"children":681},[682,683,684,685],{"id":263,"depth":672,"text":266},{"id":280,"depth":672,"text":283},{"id":295,"depth":672,"text":298},{"id":310,"depth":672,"text":313},{"id":328,"depth":675,"text":331,"children":687},[688,689],{"id":334,"depth":672,"text":337},{"id":349,"depth":672,"text":352},{"id":366,"depth":675,"text":369,"children":691},[692,693],{"id":372,"depth":672,"text":375},{"id":387,"depth":672,"text":390},{"id":405,"depth":675,"text":408,"children":695},[696,697],{"id":411,"depth":672,"text":414},{"id":426,"depth":672,"text":429},{"id":446,"depth":675,"text":449,"children":699},[700,701,702],{"id":452,"depth":672,"text":455},{"id":467,"depth":672,"text":470},{"id":482,"depth":672,"text":485},{"id":500,"depth":675,"text":503,"children":704},[705,706],{"id":506,"depth":672,"text":509},{"id":521,"depth":672,"text":524},{"id":539,"depth":675,"text":542},{"id":594,"depth":675,"text":594},"markdown","content:topics:realtime-applications-guide.md","content","topics/realtime-applications-guide.md","topics/realtime-applications-guide","md",[],1782088269536]