概要
Server-Sent Events(服务器发送事件,简称 SSE) 是一种用于实现服务器向客户端推送实时更新的技术。与 WebSocket 不同,SSE 是一种单向通信技术,适用于只需要从服务器到客户端的数据流场景。
可以理解为打字机的输出方式,一般AI产品都是这种方式,优点是一次响应的时间太久而导致http超时断开。
一、SSE技术原理
单向通信机制:基于HTTP协议实现服务器到客户端的单向数据推送,客户端通过EventSource API建立持久连接
长连接特性:使用text/event-stream内容类型,服务器保持连接开放状态持续发送事件流
自动重连机制:内置心跳检测和断线重连功能,通过retry字段指定重连间隔
事件格式规范:每条消息包含data(必需)和可选字段(event/id/retry),以\n\n分隔不同事件
二、协议对比分析
| 特性 | SSE | HTTP | WebSocket | MQ |
|---|---|---|---|---|
| 通信方向 | 单向(服务器→客户端) | 单向(客户端→服务器) | 双向全双工通信 | 发布订阅 |
| 协议基础 | HTTP长连接 | HTTP | 独立协议(ws://) | mqtt等 |
| 数据格式 | 仅文本 | 支持文本和二进制 | 支持文本和二进制 | 字节 |
| 实现复杂度 | 简单,无需额外协议支持 | 简单 | 需要专门协议支持 | 中等 |
| 优缺点 | 复用HTTP协议但保持长连接,避免频繁建立新连接的开销 | 短连接 | 长连接 | 保证消息可靠性,适用网络不好和高并发要求 |
| 应用场景 | 类似ChatGPT的渐进式响应输出体验,如新闻推送、股票行情、实时日志监控 | 适用于请求-响应模式的常规交互,如网页加载、API调用 | 适用于全双工实时通信场景,如在线聊天、协同编辑、游戏 | 适用于异步解耦、削峰填谷的场景,如订单处理、事件驱动架构 |
| 技术选型建议 | 轻量级、兼容HTTP协议,适合浏览器端无需双向通信的场景,但需注意浏览器兼容性 | 简单易用,但实时性较差;长轮询可减少延迟,但服务器资源消耗较高,适合低频更新场景 | 低延迟、高效,需浏览器和服务器双向支持,适合高频交互,但需处理连接稳定性问题 | 分布式系统首选,支持持久化和高吞吐,但需额外维护中间件,适合服务间通信而非直接前端交互 |
客户端代码java版本
以下是关于SSE技术原理、与其他协议的对比以及应用场景的详细说明:
package com.ai;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class HttpURLConnectionClient {
public static void main(String[] args) {
String sseUrl = "http://ip:port/chat-messages";
try {
// 1. 创建URL对象
URL url = new URL(sseUrl);
// 2. 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 3. 设置请求方法为POST
connection.setRequestMethod("POST");
connection.setRequestProperty("Accept", "text/event-stream");
connection.setConnectTimeout(5000);
connection.setReadTimeout(0); // 长连接
// 4. 设置请求头
connection.setRequestProperty("Authorization", "Bearer xxx");
connection.setRequestProperty("Content-Type", "application/json");
// 5. 启用输出流
connection.setDoOutput(true);
// 6. 准备请求体数据,业务数据,自行编写
JSONObject jsonObject = new JSONObject();
String requestBody = jsonObject.toJSONString();
byte[] postData = requestBody.getBytes(StandardCharsets.UTF_8);
// 7. 设置Content-Length头
connection.setRequestProperty("Content-Length", String.valueOf(postData.length));
// 8. 发送请求体
try (OutputStream os = connection.getOutputStream()) {
os.write(postData);
os.flush();
}
// 9. 获取响应
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("data:")) {
String responseData = line.substring(5).trim();
System.out.println(UnicodeConverter.unicodeToString(responseData));
// System.out.println("收到消息: " + line.substring(5).trim());
}
}
reader.close();
// 10. 关闭连接
connection.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
UnicodeConverter.java用于unicode与中文切换
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class UnicodeConverter {
public static void main(String[] args) {
String unicodeStr = "\u5173\u4e8e\u63d2\u5ea7";
System.out.println("Unicode转中文: " + unicodeToString(unicodeStr));
String chineseStr = "你好,世界!";
System.out.println("中文转Unicode: " +stringToUnicode(chineseStr));
}
// Unicode转中文
public static String unicodeToString(String str) {
Pattern pattern = Pattern.compile("(\\\\u[0-9a-fA-F]{4})");
Matcher matcher = pattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
String group = matcher.group(1);
char ch = (char) Integer.parseInt(group.substring(2), 16);
matcher.appendReplacement(sb, String.valueOf(ch));
}
matcher.appendTail(sb);
return sb.toString();
}
// 中文转Unicode
public static String stringToUnicode(String str) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch > 127) {
sb.append("\\u").append(Integer.toHexString(ch));
} else {
sb.append(ch);
}
}
return sb.toString();
}
}
使用原生JavaScript实现SSE客户端
SSE(Server-Sent Events)允许服务器通过HTTP连接向客户端推送事件。以下是实现SSE客户端的核心代码:
const eventSource = new EventSource('你的SSE服务端URL');
eventSource.onopen = function() {
console.log('SSE连接已建立');
};
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
};
eventSource.addEventListener('customEvent', function(event) {
console.log('自定义事件:', event.data);
});
eventSource.onerror = function(error) {
console.error('SSE错误:', error);
if (eventSource.readyState === EventSource.CLOSED) {
console.log('连接已关闭');
}
};
处理不同类型的服务器事件
SSE可以处理多种事件类型,包括默认消息和自定义事件:
// 处理未指定类型的消息
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data);
updateUI(data);
};
// 处理特定类型的自定义事件
eventSource.addEventListener('priceUpdate', function(event) {
const priceData = JSON.parse(event.data);
updatePriceDisplay(priceData);
});
// 处理带ID的事件(可帮助断线重连)
eventSource.addEventListener('userNotification', function(event) {
const lastEventId = event.lastEventId;
const notification = JSON.parse(event.data);
storeLastEventId(lastEventId);
showNotification(notification);
});
连接管理和错误处理
稳健的SSE客户端需要完善的连接管理:
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
eventSource.onerror = function() {
if (eventSource.readyState === EventSource.CLOSED) {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
setTimeout(() => {
reconnectAttempts++;
initSSEConnection();
}, 1000 * Math.pow(2, reconnectAttempts));
} else {
showConnectionError();
}
}
};
function initSSEConnection() {
if (eventSource) eventSource.close();
eventSource = new EventSource('服务端URL?lastEventId=' + getLastEventId());
}
浏览器兼容性处理
虽然现代浏览器都支持SSE,但仍需考虑兼容性:
if (typeof EventSource !== 'undefined') {
// 正常初始化SSE连接
} else {
// 降级方案:使用轮询或WebSocket
console.warn('浏览器不支持Server-Sent Events');
setupPollingFallback();
}
function setupPollingFallback() {
let lastUpdate = 0;
setInterval(() => {
fetch('/polling-endpoint?since=' + lastUpdate)
.then(response => response.json())
.then(data => {
lastUpdate = Date.now();
processUpdates(data);
});
}, 3000); // 每3秒轮询一次
}
安全注意事项
实现SSE时需要关注的安全问题:
// 确保使用HTTPS协议
const sseUrl = window.location.protocol === 'https:'
? 'https://api.example.com/sse'
: 'http://api.example.com/sse';
// 添加认证令牌
const token = getAuthToken();
const eventSource = new EventSource(`${sseUrl}?token=${token}`);
// 验证消息来源
eventSource.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
if (!data.signature || !verifySignature(data)) {
throw new Error('消息签名验证失败');
}
processValidData(data);
} catch (err) {
console.error('消息处理错误:', err);
}
};
实际应用示例
股票行情实时更新实现:
const stockEventSource = new EventSource('/stocks/stream');
stockEventSource.addEventListener('stockUpdate', function(event) {
const update = JSON.parse(event.data);
const stockElement = document.getElementById(`stock-${update.symbol}`);
if (stockElement) {
stockElement.querySelector('.price').textContent = update.price;
stockElement.querySelector('.change').textContent = update.change;
// 添加价格变化动画
stockElement.classList.add('updated');
setTimeout(() => stockElement.classList.remove('updated'), 500);
}
});
// 处理市场状态变化
stockEventSource.addEventListener('marketStatus', function(event) {
const status = JSON.parse(event.data);
document.getElementById('market-status').textContent = status.isOpen
? '市场开市' : '市场闭市';
});

2094

被折叠的 条评论
为什么被折叠?



