还在用 WebSocket 做实时通信?SSE 可能更简单

大家好,我是大华!在现代的Web开发中,实时通信需求越来越普遍。比如在线聊天、实时数据监控、消息推送等场景。

面对这些需求,我们通常有两种选择:SSEWebSocket。它们都能实现实时通信,但设计理念和适用场景却有很大不同。

什么是 SSE?

SSE(Server-Sent Events)是一种基于 HTTP 的服务器推送技术。它的核心特点是:单向通信,只能由服务器向客户端发送数据。

SSE 的核心特点

  • 基于 HTTP 协议:使用标准的 HTTP/1.1 协议
  • 单向通信:服务器 → 客户端
  • 自动重连:浏览器内置重连机制
  • 简单易用:API 设计简洁直观
  • 文本传输:主要支持 UTF-8 文本数据

SSE 使用示例

客户端JS代码:

javascript 复制代码
// 创建 SSE 连接
const eventSource = new EventSource('/api/real-time-data');

// 监听服务器推送的消息
eventSource.onmessage = function(event) {
  const data = JSON.parse(event.data);
  console.log('收到实时数据:', data);
  updateUI(data); // 更新界面
};

// 监听自定义事件类型
eventSource.addEventListener('systemAlert', function(event) {
  const alertData = JSON.parse(event.data);
  showAlert(alertData.message);
});

// 错误处理 - 自动重连是内置的
eventSource.onerror = function(event) {
  console.log('连接异常,正在自动重连...');
};

服务器端代码(Node.js + Express):

javascript 复制代码
app.get('/api/real-time-data', (req, res) => {
  // 设置 SSE 必需的响应头
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });
  
  // 发送初始连接确认
  res.write('data: {"status": "connected"}\n\n');
  
  // 模拟实时数据推送
  let count = 0;
  const interval = setInterval(() => {
    const data = {
      id: count++,
      timestamp: new Date().toISOString(),
      value: Math.random() * 100
    };
    
    // SSE 标准格式:data: 开头,两个换行符结尾
    res.write(`data: ${JSON.stringify(data)}\n\n`);
    
    // 每10秒发送一次系统状态
    if (count % 10 === 0) {
      res.write('event: systemAlert\n');
      res.write(`data: {"message": "系统运行正常"}\n\n`);
    }
  }, 1000);
  
  // 客户端断开连接时清理资源
  req.on('close', () => {
    clearInterval(interval);
    console.log('客户端断开连接');
  });
});

什么是 WebSocket?

WebSocket 是一种真正的全双工通信协议,允许服务器和客户端之间建立持久连接,进行双向实时通信。

WebSocket 的核心特点

  • 独立协议:基于 TCP 的独立协议(ws:// 或 wss://)
  • 双向通信:服务器 ↔ 客户端
  • 低延迟:建立连接后开销极小
  • 数据多样:支持文本和二进制数据
  • 手动管理:需要手动处理连接状态

WebSocket 使用示例

客户端JS代码:

javascript 复制代码
class ChatClient {
  constructor() {
    this.socket = null;
    this.isConnected = false;
  }
  
  connect() {
    this.socket = new WebSocket('wss://api.example.com/chat');
    
    this.socket.onopen = () => {
      this.isConnected = true;
      console.log('WebSocket 连接已建立');
      this.send({ type: 'join', username: '小明' });
    };
    
    this.socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      this.handleMessage(data);
    };
    
    this.socket.onclose = () => {
      this.isConnected = false;
      console.log('连接已断开');
      this.attemptReconnect();
    };
    
    this.socket.onerror = (error) => {
      console.error('WebSocket 错误:', error);
    };
  }
  
  sendMessage(content) {
    if (this.isConnected) {
      this.send({
        type: 'message',
        content: content,
        timestamp: Date.now()
      });
    }
  }
  
  send(data) {
    this.socket.send(JSON.stringify(data));
  }
  
  handleMessage(data) {
    switch (data.type) {
      case 'chat':
        this.displayMessage(data);
        break;
      case 'userJoin':
        this.showUserJoin(data.username);
        break;
    }
  }
  
  attemptReconnect() {
    setTimeout(() => {
      console.log('尝试重新连接...');
      this.connect();
    }, 3000);
  }
}

服务器端代码(Node.js + ws 库):

javascript 复制代码
const WebSocket = require('ws');
const wss = new WebSocket.Server({ 
  port: 8080,
  perMessageDeflate: false
});

// 存储连接的用户
const connectedUsers = new Map();

wss.on('connection', (ws, request) => {
  console.log('新的客户端连接');
  
  let currentUser = null;
  
  ws.on('message', (rawData) => {
    try {
      const data = JSON.parse(rawData);
      
      switch (data.type) {
        case 'join':
          currentUser = data.username;
          connectedUsers.set(ws, currentUser);
          
          // 广播用户加入消息
          broadcast({
            type: 'userJoin',
            username: currentUser,
            time: new Date().toISOString()
          }, ws);
          
          // 发送欢迎消息
          ws.send(JSON.stringify({
            type: 'system',
            message: `欢迎 ${currentUser} 加入聊天室!`
          }));
          break;
          
        case 'message':
          // 广播聊天消息
          broadcast({
            type: 'chat',
            username: currentUser,
            message: data.content,
            timestamp: data.timestamp
          });
          break;
      }
    } catch (error) {
      console.error('消息解析错误:', error);
      ws.send(JSON.stringify({
        type: 'error',
        message: '消息格式错误'
      }));
    }
  });
  
  ws.on('close', () => {
    if (currentUser) {
      connectedUsers.delete(ws);
      // 广播用户离开
      broadcast({
        type: 'userLeave',
        username: currentUser,
        time: new Date().toISOString()
      });
    }
    console.log('客户端断开连接');
  });
  
  // 心跳检测
  const heartbeat = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'ping' }));
    }
  }, 30000);
  
  ws.on('close', () => {
    clearInterval(heartbeat);
  });
});

function broadcast(data, excludeWs = null) {
  wss.clients.forEach((client) => {
    if (client !== excludeWs && client.readyState === WebSocket.OPEN) {
      client.send(JSON.stringify(data));
    }
  });
}

区别对比

特性 SSE WebSocket
通信模式 单向(服务器推客户端) 双向(全双工通信)
协议基础 HTTP/1.1 独立的 WebSocket 协议
连接建立 普通 HTTP 请求 HTTP 升级握手
数据格式 文本(事件流格式) 文本和二进制帧
重连机制 浏览器自动处理 需要手动实现
头部开销 每次消息带 HTTP 头 建立后极小帧头
兼容性 良好(除 IE) 优秀(IE10+)
开发复杂度 简单直观 相对复杂

适用场景

推荐使用 SSE 的场景

1. 实时数据监控面板 2. 实时消息通知 3. 实时数据流展示

比如:股票价格实时更新、体育比赛比分直播、物流订单状态跟踪和服务器日志实时显示等。

推荐使用 WebSocket 的场景

1. 实时交互应用 2. 实时游戏应用 3. 实时音视频通信

比如:视频会议系统、在线客服聊天和实时协作编辑文档等

如何选择?

选择 SSE:

  • 只需要服务器向客户端推送数据
  • 希望快速实现、简单维护
  • 项目对移动端兼容性要求高
  • 数据更新频率适中(秒级)
  • 不需要传输二进制数据

选择 WebSocket:

  • 需要真正的双向实时通信
  • 数据传输频率很高(毫秒级)
  • 需要传输二进制数据(如图片、音频)
  • 构建实时交互应用(游戏、协作工具)
  • 对延迟极其敏感的场景

混合使用策略

在一些复杂应用中,可以同时使用两者:

javascript 复制代码
class HybridApp {
  constructor() {
    // 使用 SSE 接收通知和广播消息
    this.notificationSource = new EventSource('/api/notifications');
    
    // 使用 WebSocket 进行实时交互
    this.interactionSocket = new WebSocket('wss://api.example.com/interact');
    
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    // SSE 处理广播类消息
    this.notificationSource.onmessage = (event) => {
      this.handleBroadcastMessage(JSON.parse(event.data));
    };
    
    // WebSocket 处理交互类消息
    this.interactionSocket.onmessage = (event) => {
      this.handleInteractionMessage(JSON.parse(event.data));
    };
  }
}

总结

SSE 的优势在于简单易用、自动重连、与 HTTP 基础设施完美集成,适合服务器向客户端的单向数据推送场景。

WebSocket 的优势在于真正的双向通信、低延迟、支持二进制数据,适合需要高频双向交互的复杂应用。

在实际项目中,可以根据具体的业务需求、性能要求来做出合理的技术选型。

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《SpringBoot+Vue3 整合 SSE 实现实时消息推送》

《这20条SQL优化方案,让你的数据库查询速度提升10倍》

《SpringBoot 动态菜单权限系统设计的企业级解决方案》

《Vue3 + ElementPlus 动态菜单实现:一套代码完美适配多角色权限系统》

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax