SSE 与 WebSocket:实时通信双子星详解
在当今的 Web 应用中,实时通信已成为许多场景的必备功能。无论是聊天应用、实时数据监控还是在线协作工具,都需要服务器能够主动向客户端推送数据。在实现实时 Web 应用时,Server-Sent Events (SSE) 和 WebSocket 是两种最常用的技术方案。本文将深入探讨这两种技术的工作原理、适用场景及优缺点对比。
什么是 Server-Sent Events (SSE)
基本概念
Server-Sent Events (SSE) 是一种允许服务器向客户端单向推送数据的 HTML5 技术。它基于标准的 HTTP 协议,使用简单的文本格式传输数据。
工作原理
SSE 通过在客户端和服务器之间建立持久的 HTTP 连接来实现数据推送:
-
客户端 通过
EventSourceAPI 向服务器发起请求 -
服务器 保持连接打开,并以
text/event-stream格式发送数据 -
数据 以特定格式流式传输,支持事件类型和数据标识
客户端实现
// 创建 EventSource 实例连接至 SSE 端点
const eventSource = new EventSource('/api/events');
// 监听默认事件
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
};
// 监听自定义事件
eventSource.addEventListener('update', function(event) {
const data = JSON.parse(event.data);
updateUI(data);
});
// 处理错误
eventSource.onerror = function(event) {
console.error('SSE 连接错误:', event);
};
服务器端实现 (Node.js/Express)
app.get('/api/events', (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: ' + JSON.stringify({ status: '连接成功' }) + '\n\n');
// 定期发送数据
const intervalId = setInterval(() => {
const data = {
timestamp: new Date().toISOString(),
value: Math.random() * 100
};
// SSE 数据格式
res.write('event: update\n');
res.write('data: ' + JSON.stringify(data) + '\n\n');
}, 2000);
// 客户端断开连接时清理
req.on('close', () => {
clearInterval(intervalId);
});
});
SSE 数据格式
SSE 使用简单的文本格式,每个消息由字段组成,以两个换行符结尾:
text
event: notification
data: 这是第一条消息
data: 这是第二条消息
id: 12345
retry: 3000
event: update
data: {"value": 42, "timestamp": "2023-01-01T12:00:00Z"}
id: 12346
什么是 WebSocket
基本概念
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,允许客户端和服务器之间进行双向实时数据交换。
工作原理
WebSocket 通过 HTTP 升级机制建立连接:
-
客户端 发送带有
Upgrade: websocket头的 HTTP 请求 -
服务器 返回
101 Switching Protocols响应确认协议升级 -
双方 通过 WebSocket 协议进行双向通信
客户端实现
// 创建 WebSocket 连接
const socket = new WebSocket('ws://localhost:8080/ws');
// 连接打开时
socket.onopen = function(event) {
console.log('WebSocket 连接已建立');
// 发送消息到服务器
socket.send(JSON.stringify({ type: 'greeting', message: 'Hello Server!' }));
};
// 接收服务器消息
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
};
// 处理错误
socket.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
// 连接关闭时
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭:', event.code, event.reason);
};
// 发送消息
function sendMessage(message) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
}
}
服务器端实现 (Node.js/ws 库)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
console.log('新的 WebSocket 连接');
// 向客户端发送欢迎消息
ws.send(JSON.stringify({
type: 'welcome',
message: '连接成功',
timestamp: new Date().toISOString()
}));
// 接收客户端消息
ws.on('message', function incoming(data) {
console.log('收到客户端消息:', data.toString());
const message = JSON.parse(data);
// 处理不同类型的消息
switch (message.type) {
case 'chat':
// 广播消息给所有连接的客户端
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'chat',
user: message.user,
text: message.text,
timestamp: new Date().toISOString()
}));
}
});
break;
case 'ping':
ws.send(JSON.stringify({ type: 'pong', timestamp: new Date().toISOString() }));
break;
}
});
// 定期发送更新
const intervalId = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'update',
data: Math.random() * 100,
timestamp: new Date().toISOString()
}));
}
}, 5000);
// 连接关闭时清理
ws.on('close', function close() {
console.log('WebSocket 连接已关闭');
clearInterval(intervalId);
});
});
SSE 与 WebSocket 的详细对比
协议与通信模式
| 特性 | SSE | WebSocket |
|---|---|---|
| 协议基础 | HTTP | 独立的 WebSocket 协议 |
| 通信模式 | 服务器到客户端的单向通信 | 客户端和服务器之间的双向通信 |
| 连接建立 | 标准的 HTTP GET 请求 | HTTP 升级请求 |
| 数据格式 | 文本 (UTF-8) | 文本和二进制数据 |
浏览器兼容性
SSE 浏览器支持:
-
Chrome 6+
-
Firefox 6+
-
Safari 5+
-
Edge 79+
-
不支持 IE
WebSocket 浏览器支持:
-
Chrome 4+
-
Firefox 4+
-
Safari 5+
-
Edge 12+
-
IE 10+
性能与资源消耗
| 方面 | SSE | WebSocket |
|---|---|---|
| 连接开销 | 每个连接一个 HTTP 请求 | 初始握手后持续连接 |
| 数据传输效率 | 文本格式,有一定开销 | 帧格式,开销较小 |
| 内存使用 | 相对较低 | 相对较高 |
| 服务器资源 | 每个连接保持一个 HTTP 连接 | 每个连接保持一个 TCP 连接 |
功能特性对比
| 功能 | SSE | WebSocket |
|---|---|---|
| 自动重连 | ✅ 内置支持 | ❌ 需要手动实现 |
| 事件类型 | ✅ 支持自定义事件 | ❌ 需要自行实现事件系统 |
| 消息 ID | ✅ 内置支持,可追踪消息 | ❌ 需要自行实现 |
| 二进制数据 | ❌ 仅支持文本 | ✅ 完整支持 |
| 双向通信 | ❌ 仅服务器到客户端 | ✅ 完整的双向通信 |
安全性考虑
SSE 安全性:
-
遵循同源策略
-
支持 CORS
-
可使用标准 HTTP 安全措施 (HTTPS、认证等)
WebSocket 安全性:
-
遵循同源策略,但可配置
-
容易受到跨站 WebSocket 劫持攻击
-
需要额外验证 Origin 头
实际应用场景
适合使用 SSE 的场景
-
实时通知系统
-
社交媒体更新
-
新闻推送
-
系统警报
-
-
实时数据监控
-
股票价格更新
-
服务器状态监控
-
IoT 设备数据流
-
-
实时仪表板
-
业务指标展示
-
实时分析数据
-
示例:股票价格监控
// 客户端
const stockSource = new EventSource('/api/stocks');
stockSource.addEventListener('price_update', function(event) {
const update = JSON.parse(event.data);
updateStockPrice(update.symbol, update.price, update.change);
});
stockSource.addEventListener('market_news', function(event) {
const news = JSON.parse(event.data);
displayMarketNews(news.headline, news.timestamp);
});
适合使用 WebSocket 的场景
-
实时聊天应用
-
一对一聊天
-
群组聊天
-
在线客服
-
-
多人协作工具
-
协同文档编辑
-
白板应用
-
项目管理工具
-
-
在线游戏
-
多玩家游戏
-
实时竞技游戏
-
-
实时位置追踪
-
配送跟踪
-
车队管理
-
示例:实时聊天应用
javascript
// 客户端
const chatSocket = new WebSocket('wss://example.com/chat');
chatSocket.onmessage = function(event) {
const message = JSON.parse(event.data);
switch(message.type) {
case 'user_joined':
displaySystemMessage(`${message.username} 加入了聊天`);
break;
case 'user_left':
displaySystemMessage(`${message.username} 离开了聊天`);
break;
case 'chat_message':
displayChatMessage(message.username, message.text, message.timestamp);
break;
}
};
function sendChatMessage(text) {
chatSocket.send(JSON.stringify({
type: 'chat_message',
text: text,
timestamp: new Date().toISOString()
}));
}
高级技巧与最佳实践
SSE 高级用法
连接管理与重试
javascript
function createSSEConnection(url, options = {}) {
const eventSource = new EventSource(url);
eventSource.onopen = function() {
console.log('SSE 连接已建立');
options.onOpen && options.onOpen();
};
eventSource.onerror = function() {
console.error('SSE 连接错误');
options.onError && options.onError();
// 实现自定义重连逻辑
if (eventSource.readyState === EventSource.CLOSED) {
setTimeout(() => {
console.log('尝试重新连接...');
createSSEConnection(url, options);
}, options.retryTimeout || 5000);
}
};
// 注册事件处理器
if (options.events) {
Object.keys(options.events).forEach(eventName => {
eventSource.addEventListener(eventName, options.events[eventName]);
});
}
return eventSource;
}
// 使用示例
const sse = createSSEConnection('/api/events', {
onOpen: () => console.log('连接成功'),
onError: () => console.log('连接失败'),
retryTimeout: 3000,
events: {
update: (event) => handleUpdate(JSON.parse(event.data)),
notification: (event) => showNotification(JSON.parse(event.data))
}
});
WebSocket 高级用法
连接状态管理与重连
javascript
class WebSocketManager {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.socket = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
this.reconnectInterval = options.reconnectInterval || 3000;
this.eventHandlers = new Map();
this.connect();
}
connect() {
try {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('WebSocket 连接已建立');
this.reconnectAttempts = 0;
this.options.onOpen && this.options.onOpen();
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.emit('message', data);
};
this.socket.onclose = (event) => {
console.log('WebSocket 连接已关闭:', event.code, event.reason);
this.options.onClose && this.options.onClose(event);
if (event.code !== 1000) { // 非正常关闭
this.handleReconnect();
}
};
this.socket.onerror = (error) => {
console.error('WebSocket 错误:', error);
this.options.onError && this.options.onError(error);
};
} catch (error) {
console.error('创建 WebSocket 连接失败:', error);
this.handleReconnect();
}
}
handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
console.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
setTimeout(() => {
this.connect();
}, this.reconnectInterval * this.reconnectAttempts); // 指数退避
} else {
console.error('达到最大重连次数,停止重连');
this.emit('reconnect_failed');
}
}
send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
return true;
} else {
console.warn('WebSocket 未连接,消息发送失败');
return false;
}
}
on(event, handler) {
if (!this.eventHandlers.has(event)) {
this.eventHandlers.set(event, []);
}
this.eventHandlers.get(event).push(handler);
}
emit(event, data) {
const handlers = this.eventHandlers.get(event);
if (handlers) {
handlers.forEach(handler => handler(data));
}
}
close() {
if (this.socket) {
this.socket.close(1000, '正常关闭');
}
}
}
// 使用示例
const wsManager = new WebSocketManager('wss://example.com/ws', {
onOpen: () => console.log('连接成功'),
onClose: () => console.log('连接关闭'),
maxReconnectAttempts: 10,
reconnectInterval: 2000
});
wsManager.on('message', (data) => {
console.log('收到消息:', data);
});
wsManager.send({ type: 'ping', timestamp: Date.now() });
混合使用方案
在某些复杂应用中,可以同时使用 SSE 和 WebSocket 来发挥各自的优势:
javascript
class RealTimeCommunication {
constructor(options) {
this.options = options;
this.sse = null;
this.websocket = null;
}
// 使用 SSE 接收服务器推送的通知和数据更新
setupSSE() {
this.sse = new EventSource('/api/notifications');
this.sse.addEventListener('notification', (event) => {
this.handleNotification(JSON.parse(event.data));
});
this.sse.addEventListener('data_update', (event) => {
this.handleDataUpdate(JSON.parse(event.data));
});
}
// 使用 WebSocket 进行双向通信
setupWebSocket() {
this.websocket = new WebSocketManager('wss://example.com/chat');
this.websocket.on('message', (data) => {
switch(data.type) {
case 'chat_message':
this.handleChatMessage(data);
break;
case 'user_activity':
this.handleUserActivity(data);
break;
}
});
}
// 发送聊天消息
sendChatMessage(message) {
this.websocket.send({
type: 'chat_message',
content: message,
timestamp: new Date().toISOString()
});
}
// 处理通知
handleNotification(notification) {
console.log('收到通知:', notification);
// 显示通知 UI
}
// 处理数据更新
handleDataUpdate(update) {
console.log('数据更新:', update);
// 更新应用状态
}
// 处理聊天消息
handleChatMessage(message) {
console.log('聊天消息:', message);
// 显示聊天消息
}
// 处理用户活动
handleUserActivity(activity) {
console.log('用户活动:', activity);
// 更新用户状态
}
}
总结与选择建议
选择 SSE 的情况:
-
只需要服务器向客户端推送数据
-
需要简单的实现和自动重连机制
-
项目对浏览器兼容性要求较高(相比 WebSocket)
-
数据传输以文本为主
-
可以利用现有的 HTTP 基础设施和安全机制
选择 WebSocket 的情况:
-
需要客户端和服务器之间的双向通信
-
需要传输二进制数据(如图片、音频、视频)
-
对延迟和性能有极高要求(如游戏、实时交易)
-
需要实现复杂的交互场景
现代 Web 开发的趋势
随着现代 Web 应用复杂度的增加,许多开发者开始使用专门的实时通信库,如 Socket.IO、SignalR 等,这些库在底层自动选择最佳传输方式(包括 SSE、WebSocket 甚至轮询),并提供更高级的抽象和功能。
无论选择哪种技术,理解其底层原理和特性都是构建高效、可靠实时应用的关键。希望本文能帮助你在实际项目中做出更合适的技术选择。
更多推荐


所有评论(0)