注册
web

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

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


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


什么是 SSE?


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


SSE 的核心特点



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

SSE 使用示例


客户端JS代码:


// 创建 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):


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代码:


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 库):


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));
}
});
}

区别对比


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

适用场景


推荐使用 SSE 的场景


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


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


推荐使用 WebSocket 的场景


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


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


如何选择?


选择 SSE:



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

选择 WebSocket:



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

混合使用策略


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


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


作者:程序员大华
来源:juejin.cn/post/7576559408659611700

0 个评论

要回复文章请先登录注册