html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SSE与轮询技术对比</title>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--danger-color: #e74c3c;
--dark-color: #2c3e50;
--light-color: #ecf0f1;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: white;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
h1 {
color: var(--dark-color);
margin-bottom: 10px;
}
.subtitle {
color: #7f8c8d;
font-size: 1.1rem;
}
.container {
display: flex;
gap: 20px;
max-width: 1400px;
margin: 0 auto;
}
@media (max-width: 768px) {
.container {
flex-direction: column;
}
}
.panel {
flex: 1;
background: white;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
overflow: hidden;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 15px 20px;
background: var(--dark-color);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.sse .panel-header {
background: var(--primary-color);
}
.polling .panel-header {
background: var(--secondary-color);
}
.status {
display: flex;
align-items: center;
gap: 8px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #e74c3c;
}
.status.connected .status-indicator {
background: #2ecc71;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.panel-body {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
}
.control-buttons {
display: flex;
gap: 10px;
}
button {
padding: 10px 15px;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-success {
background: var(--secondary-color);
color: white;
}
.btn-danger {
background: var(--danger-color);
color: white;
}
button:hover {
opacity: 0.9;
transform: translateY(-2px);
}
button:disabled {
background: #bdc3c7;
cursor: not-allowed;
transform: none;
}
.message-area {
flex: 1;
border: 1px solid #ddd;
border-radius: var(--border-radius);
padding: 15px;
overflow-y: auto;
max-height: 300px;
background: #f9f9f9;
}
.message {
padding: 10px;
margin-bottom: 10px;
border-radius: var(--border-radius);
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.message-time {
font-size: 0.8rem;
color: #7f8c8d;
margin-bottom: 5px;
}
.message-content {
font-weight: 500;
}
.stats {
display: flex;
justify-content: space-between;
background: #f1f2f6;
padding: 10px 15px;
border-radius: var(--border-radius);
font-size: 0.9rem;
}
.comparison {
max-width: 1400px;
margin: 30px auto;
background: white;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
overflow: hidden;
}
.comparison-header {
padding: 15px 20px;
background: var(--dark-color);
color: white;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
}
.comparison-table th, .comparison-table td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
.comparison-table th {
background: #f8f9fa;
font-weight: 600;
}
.comparison-table tr:last-child td {
border-bottom: none;
}
.pros-cons {
display: flex;
gap: 20px;
margin-top: 20px;
}
.pros, .cons {
flex: 1;
padding: 15px;
border-radius: var(--border-radius);
}
.pros {
background: rgba(46, 204, 113, 0.1);
border-left: 4px solid var(--secondary-color);
}
.cons {
background: rgba(231, 76, 60, 0.1);
border-left: 4px solid var(--danger-color);
}
.pros h3, .cons h3 {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.pros ul, .cons ul {
padding-left: 20px;
}
.pros li, .cons li {
margin-bottom: 8px;
}
footer {
text-align: center;
margin-top: 40px;
padding: 20px;
color: #7f8c8d;
font-size: 0.9rem;
}
</style>
</head>
<body>
<header>
<h1>SSE与轮询技术对比</h1>
<p class="subtitle">实时通信技术的实现方式与性能比较</p>
</header>
<div class="container">
<!-- SSE 面板 -->
<div class="panel sse">
<div class="panel-header">
<h2>Server-Sent Events (SSE)</h2>
<div class="status">
<div class="status-indicator"></div>
<span>未连接</span>
</div>
</div>
<div class="panel-body">
<div class="control-buttons">
<button id="sse-connect" class="btn-primary">连接</button>
<button id="sse-disconnect" class="btn-danger" disabled>断开</button>
<button id="sse-send" class="btn-success" disabled>发送测试消息</button>
</div>
<div class="message-area" id="sse-messages">
<div class="message">
<div class="message-time">等待连接...</div>
<div class="message-content">点击"连接"按钮开始SSE演示</div>
</div>
</div>
<div class="stats">
<div>消息数量: <span id="sse-count">0</span></div>
<div>最后接收: <span id="sse-last">-</span></div>
<div>连接状态: <span id="sse-status">未连接</span></div>
</div>
</div>
</div>
<!-- 轮询面板 -->
<div class="panel polling">
<div class="panel-header">
<h2>轮询 (Polling)</h2>
<div class="status">
<div class="status-indicator"></div>
<span>未启动</span>
</div>
</div>
<div class="panel-body">
<div class="control-buttons">
<button id="polling-start" class="btn-primary">开始轮询</button>
<button id="polling-stop" class="btn-danger" disabled>停止轮询</button>
<button id="polling-send" class="btn-success" disabled>发送测试消息</button>
</div>
<div class="message-area" id="polling-messages">
<div class="message">
<div class="message-time">等待启动...</div>
<div class="message-content">点击"开始轮询"按钮开始轮询演示</div>
</div>
</div>
<div class="stats">
<div>消息数量: <span id="polling-count">0</span></div>
<div>最后接收: <span id="polling-last">-</span></div>
<div>轮询间隔: <span id="polling-interval">2000ms</span></div>
</div>
</div>
</div>
</div>
<div class="comparison">
<div class="comparison-header">
<h2>技术对比</h2>
</div>
<div class="panel-body">
<table class="comparison-table">
<thead>
<tr>
<th>特性</th>
<th>Server-Sent Events (SSE)</th>
<th>轮询 (Polling)</th>
</tr>
</thead>
<tbody>
<tr>
<td>通信方向</td>
<td>服务器到客户端的单向通信</td>
<td>客户端到服务器的双向通信</td>
</tr>
<tr>
<td>协议</td>
<td>基于HTTP,使用长连接</td>
<td>基于HTTP,使用短连接</td>
</tr>
<tr>
<td>实时性</td>
<td>高,服务器可立即推送消息</td>
<td>取决于轮询间隔</td>
</tr>
<tr>
<td>网络开销</td>
<td>低,保持单一连接</td>
<td>高,频繁建立/关闭连接</td>
</tr>
<tr>
<td>服务器负载</td>
<td>较低,连接复用</td>
<td>较高,频繁处理请求</td>
</tr>
<tr>
<td>浏览器支持</td>
<td>现代浏览器普遍支持</td>
<td>所有浏览器支持</td>
</tr>
<tr>
<td>实现复杂度</td>
<td>中等,需要服务器端支持</td>
<td>简单,标准HTTP请求</td>
</tr>
</tbody>
</table>
<div class="pros-cons">
<div class="pros">
<h3>✅ SSE 优势</h3>
<ul>
<li>真正的实时推送</li>
<li>低延迟</li>
<li>减少网络开销</li>
<li>自动重连机制</li>
<li>更高效的服务器资源利用</li>
</ul>
</div>
<div class="cons">
<h3>❌ SSE 限制</h3>
<ul>
<li>单向通信(服务器到客户端)</li>
<li>最大并发连接数限制(HTTP/1.1)</li>
<li>某些代理服务器可能不支持</li>
<li>需要服务器端支持</li>
</ul>
</div>
</div>
<div class="pros-cons">
<div class="pros">
<h3>✅ 轮询 优势</h3>
<ul>
<li>实现简单</li>
<li>所有浏览器支持</li>
<li>双向通信</li>
<li>无连接数限制</li>
<li>代理和防火墙友好</li>
</ul>
</div>
<div class="cons">
<h3>❌ 轮询 限制</h3>
<ul>
<li>高延迟(取决于轮询间隔)</li>
<li>高网络开销</li>
<li>服务器负载高</li>
<li>实时性差</li>
<li>可能请求空数据</li>
</ul>
</div>
</div>
</div>
</div>
<footer>
<p>SSE与轮询技术对比演示 | 实际环境中请根据需求选择合适的技术方案</p>
</footer>
<script>
// SSE 功能实现
let sseConnection = null;
let sseMessageCount = 0;
document.getElementById('sse-connect').addEventListener('click', connectSSE);
document.getElementById('sse-disconnect').addEventListener('click', disconnectSSE);
document.getElementById('sse-send').addEventListener('click', sendSSEMessage);
function connectSSE() {
try {
// 在实际应用中,这里应该是真实的SSE端点
// 这里我们模拟一个SSE连接
sseConnection = {
close: function() {
// 模拟关闭连接
updateSSEStatus('未连接');
document.getElementById('sse-connect').disabled = false;
document.getElementById('sse-disconnect').disabled = true;
document.getElementById('sse-send').disabled = true;
}
};
updateSSEStatus('已连接');
addSSEMessage('系统', 'SSE连接已建立');
document.getElementById('sse-connect').disabled = true;
document.getElementById('sse-disconnect').disabled = false;
document.getElementById('sse-send').disabled = false;
// 模拟服务器推送消息
simulateServerMessages();
} catch (error) {
addSSEMessage('错误', '连接失败: ' + error.message);
}
}
function disconnectSSE() {
if (sseConnection) {
sseConnection.close();
sseConnection = null;
addSSEMessage('系统', 'SSE连接已断开');
}
}
function sendSSEMessage() {
// 在实际应用中,这里应该向服务器发送消息
// 这里我们只是模拟
addSSEMessage('客户端', '测试消息 ' + (++sseMessageCount));
}
function addSSEMessage(sender, content) {
const messagesContainer = document.getElementById('sse-messages');
const messageElement = document.createElement('div');
messageElement.className = 'message';
const now = new Date();
const timeString = now.toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-time">${timeString} - ${sender}</div>
<div class="message-content">${content}</div>
`;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 更新统计信息
document.getElementById('sse-count').textContent = document.querySelectorAll('#sse-messages .message').length - 1; // 减去初始消息
document.getElementById('sse-last').textContent = timeString;
}
function updateSSEStatus(status) {
const statusElement = document.querySelector('.sse .status');
const statusText = document.getElementById('sse-status');
statusText.textContent = status;
if (status === '已连接') {
statusElement.classList.add('connected');
statusElement.querySelector('span').textContent = '已连接';
} else {
statusElement.classList.remove('connected');
statusElement.querySelector('span').textContent = '未连接';
}
}
function simulateServerMessages() {
// 模拟服务器随机发送消息
if (sseConnection) {
const messages = [
'服务器时间: ' + new Date().toLocaleTimeString(),
'系统状态: 运行正常',
'用户活动: 3个活跃会话',
'数据更新: 已完成同步',
'通知: 系统维护计划于今晚进行'
];
// 随机间隔发送消息
const interval = setInterval(() => {
if (!sseConnection) {
clearInterval(interval);
return;
}
const randomMessage = messages[Math.floor(Math.random() * messages.length)];
addSSEMessage('服务器', randomMessage);
}, 3000 + Math.random() * 7000); // 3-10秒随机间隔
}
}
// 轮询功能实现
let pollingInterval = null;
let pollingMessageCount = 0;
document.getElementById('polling-start').addEventListener('click', startPolling);
document.getElementById('polling-stop').addEventListener('click', stopPolling);
document.getElementById('polling-send').addEventListener('click', sendPollingMessage);
function startPolling() {
updatePollingStatus('轮询中');
addPollingMessage('系统', '轮询已启动');
document.getElementById('polling-start').disabled = true;
document.getElementById('polling-stop').disabled = false;
document.getElementById('polling-send').disabled = false;
// 模拟轮询请求
pollingInterval = setInterval(() => {
// 在实际应用中,这里应该是向服务器发送请求
// 这里我们模拟服务器响应
const hasNewData = Math.random() > 0.7; // 70%的概率没有新数据
if (hasNewData) {
const responses = [
'数据更新可用',
'新通知: 系统性能优化',
'用户反馈: 收到新评论',
'统计信息: 今日访问量增加15%'
];
const randomResponse = responses[Math.floor(Math.random() * responses.length)];
addPollingMessage('服务器', randomResponse);
} else {
// 无新数据,不添加消息,但更新最后请求时间
const now = new Date();
document.getElementById('polling-last').textContent = now.toLocaleTimeString();
}
}, 2000); // 2秒轮询间隔
}
function stopPolling() {
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
updatePollingStatus('已停止');
addPollingMessage('系统', '轮询已停止');
document.getElementById('polling-start').disabled = false;
document.getElementById('polling-stop').disabled = true;
document.getElementById('polling-send').disabled = true;
}
}
function sendPollingMessage() {
// 在实际应用中,这里应该向服务器发送消息
// 这里我们只是模拟
addPollingMessage('客户端', '测试消息 ' + (++pollingMessageCount));
}
function addPollingMessage(sender, content) {
const messagesContainer = document.getElementById('polling-messages');
const messageElement = document.createElement('div');
messageElement.className = 'message';
const now = new Date();
const timeString = now.toLocaleTimeString();
messageElement.innerHTML = `
<div class="message-time">${timeString} - ${sender}</div>
<div class="message-content">${content}</div>
`;
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// 更新统计信息
document.getElementById('polling-count').textContent = document.querySelectorAll('#polling-messages .message').length - 1; // 减去初始消息
document.getElementById('polling-last').textContent = timeString;
}
function updatePollingStatus(status) {
const statusElement = document.querySelector('.polling .status');
if (status === '轮询中') {
statusElement.classList.add('connected');
statusElement.querySelector('span').textContent = '轮询中';
} else {
statusElement.classList.remove('connected');
statusElement.querySelector('span').textContent = status;
}
}
</script>
</body>
</html>