You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需API请求更新UI的实现方案——以动态聊天应用用户状态为例

实现无轮询的用户活动状态更新方案

你说的这种无需频繁发起请求就能实时更新用户状态的功能,核心是用长连接技术替代传统轮询,主流方案有两种:WebSocket 和 Server-Sent Events(SSE),Discord 主要采用的是 WebSocket 方案。

一、WebSocket:双向实时通信

WebSocket 是全双工通信协议,建立连接后客户端与服务器可随时互相发送消息,无需每次请求重新建立连接,因此不会在网络面板产生大量短请求记录。

前端实现示例

// 建立WebSocket连接
const socket = new WebSocket('ws://your-server-domain.com/status');

// 连接成功回调
socket.onopen = () => {
  console.log('状态更新连接已建立');
  // 发送用户身份信息,让服务器识别
  socket.send(JSON.stringify({ userId: 'user123' }));
};

// 接收服务器推送的状态更新
socket.onmessage = (event) => {
  const statusData = JSON.parse(event.data);
  // 更新页面用户状态(比如标记在线用户活跃/离线)
  updateUserStatus(statusData.userId, statusData.status);
};

// 连接断开处理
socket.onclose = () => {
  console.log('状态连接断开,尝试重连');
  setTimeout(() => initWebSocket(), 3000);
};

// 用户活动时通知服务器更新状态
function notifyUserActive() {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type: 'active', userId: 'user123' }));
  }
}

// 监听用户活动事件(节流避免频繁发送)
document.addEventListener('mousemove', throttle(notifyUserActive, 3000));
document.addEventListener('keypress', throttle(notifyUserActive, 3000));

// 节流函数
function throttle(func, delay) {
  let lastCall = 0;
  return function() {
    const now = Date.now();
    if (now - lastCall >= delay) {
      func();
      lastCall = now;
    }
  };
}

// 初始化连接函数
function initWebSocket() {
  const newSocket = new WebSocket('ws://your-server-domain.com/status');
  Object.assign(socket, newSocket);
}

后端实现示例(Node.js + ws 库)

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 存储在线用户的连接与状态
const userConnections = new Map();

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const message = JSON.parse(data);
    if (message.userId) {
      userConnections.set(message.userId, ws);
      if (message.type === 'active') {
        // 广播用户活跃状态给所有在线客户端
        const statusUpdate = { userId: message.userId, status: 'active' };
        broadcast(statusUpdate);
      }
    }
  });

  ws.on('close', () => {
    // 用户断开连接时,更新状态为离线并广播
    for (const [userId, connection] of userConnections.entries()) {
      if (connection === ws) {
        const statusUpdate = { userId, status: 'offline' };
        broadcast(statusUpdate);
        userConnections.delete(userId);
        break;
      }
    }
  });
});

// 广播状态更新给所有在线客户端
function broadcast(data) {
  const message = JSON.stringify(data);
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

二、Server-Sent Events(SSE):服务器单向推送

如果只需要服务器主动给客户端推送状态更新(比如他人在线状态变化),不需要客户端频繁发消息,SSE 是更轻量的选择——它基于 HTTP 协议,仅支持服务器到客户端的单向通信。

前端实现示例

// 建立SSE连接
const eventSource = new EventSource('http://your-server-domain.com/status-stream');

// 接收服务器推送的状态更新
eventSource.onmessage = (event) => {
  const statusData = JSON.parse(event.data);
  updateUserStatus(statusData.userId, statusData.status);
};

// 连接错误处理
eventSource.onerror = () => {
  console.error('SSE连接出错,尝试重连');
};

// 用户活动时,用单次POST请求通知服务器(非轮询)
function notifyUserActive() {
  fetch('/api/user-active', {
    method: 'POST',
    body: JSON.stringify({ userId: 'user123' }),
    headers: { 'Content-Type': 'application/json' }
  });
}

document.addEventListener('mousemove', throttle(notifyUserActive, 3000));

// 节流函数
function throttle(func, delay) {
  let lastCall = 0;
  return function() {
    const now = Date.now();
    if (now - lastCall >= delay) {
      func();
      lastCall = now;
    }
  };
}

后端实现示例(Node.js)

const http = require('http');
const fs = require('fs');

http.createServer((req, res) => {
  if (req.url === '/status-stream') {
    // 设置SSE响应头
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive'
    });

    // 模拟监听状态变化(实际可对接数据库/缓存的变更事件)
    const sendStatusUpdate = () => {
      // 从数据库获取最新用户状态
      const statusUpdate = JSON.stringify({ userId: 'user123', status: 'active' });
      res.write(`data: ${statusUpdate}\n\n`);
    };

    // 定时推送(实际应按需触发,而非定时)
    const interval = setInterval(sendStatusUpdate, 5000);

    // 客户端断开时清理
    req.on('close', () => {
      clearInterval(interval);
      console.log('SSE客户端断开');
    });
  } else if (req.url === '/api/user-active' && req.method === 'POST') {
    // 处理用户活动通知,更新服务器端状态
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      const { userId } = JSON.parse(body);
      // 更新用户状态到数据库/缓存
      // updateUserStatusInDB(userId, 'active');
      res.writeHead(200);
      res.end();
    });
  }
}).listen(3000);

为什么Discord的网络面板看不到请求?

因为 WebSocket/SSE 都是持久连接:建立连接时仅产生一次握手请求(WebSocket 是 HTTP 升级请求,SSE 是普通 GET 请求),之后所有数据都通过这个长连接传输,不会生成新的请求记录。在 Chrome 网络面板中,WebSocket 的数据帧会单独显示在「Frames」标签下,而非普通请求列表里。

内容的提问来源于stack exchange,提问作者Jaros

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 00:35:28