还在用 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 动态菜单实现:一套代码完美适配多角色权限系统》

相关推荐
鹏北海44 分钟前
多标签页登录状态同步:一个简单而有效的解决方案
前端·面试·架构
_AaronWong1 小时前
基于 Vue 3 的屏幕音频捕获实现:从原理到实践
前端·vue.js·音视频开发
孟祥_成都1 小时前
深入 Nestjs 底层概念(1):依赖注入和面向切面编程 AOP
前端·node.js·nestjs
let_code1 小时前
CopilotKit-丝滑连接agent和应用-理论篇
前端·agent·ai编程
Apifox1 小时前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
木易士心1 小时前
深入剖析:按下 F5 后,浏览器前端究竟发生了什么?
前端·javascript
在掘金801101 小时前
vue3中使用medium-zoom
前端·vue.js
xump2 小时前
如何在DevTools选中调试一个实时交互才能显示的元素样式
前端·javascript·css
折翅嘀皇虫2 小时前
fastdds.type_propagation 详解
java·服务器·前端