SSE最核心的四行代码:

header('Content-Type: text/event-stream');

header('Cache-Control: no-cache');

header('Connection: keep-alive');

header('X-Accel-Buffering: no');

这四行代码共同创建了一个适合 SSE 的长连接、实时、无缓冲的 HTTP 响应环境。

各个头部的作用

  1. header('Content-Type: text/event-stream');

    • 作用:这是最重要的头部。它告诉客户端(浏览器),服务器返回的响应体遵循 EventStream 格式规范。浏览器在接收到这个头部后,会启用 SSE API(如 EventSource 对象)来处理这个连接,并等待接收事件流。

  2. header('Cache-Control: no-cache');

    • 作用:指示浏览器(和任何中间的代理服务器)不要缓存这个响应。对于 SSE 这种实时数据流来说,缓存旧数据是毫无意义的,并且会导致客户端无法接收到最新的消息。确保每次连接获取的都是实时数据。

  3. header('Connection: keep-alive');

    • 作用:启用 HTTP 持久连接。这意味着在单个 TCP 连接上可以进行多次请求/响应,而不是每次通信后都关闭连接。对于 SSE 来说,服务器需要与客户端保持一个长时间的、开放的连接,以便持续发送数据。这个头部有助于实现这一点。

  4. header('X-Accel-Buffering: no');

    • 作用:这是一个针对 Nginx 服务器的高级配置。

    • 解释:Nginx 默认会使用缓冲代理。它会先从 PHP-FPM(后端进程)接收数据,缓冲起来,直到达到一定大小或超时后,才一次性发送给客户端。这种行为对于 SSE 是致命的,因为它会导致消息延迟,无法实现“实时”推送。

    • 设置 no:就是明确告诉 Nginx 禁用 对这个连接的缓冲。这样,PHP 脚本输出的每一个字节都会立即被 Nginx 转发给客户端,确保了数据的实时性。

    • 注意:这个头部是 Nginx 特有的,在其他 Web 服务器(如 Apache)上无效。

后端代码

这段PHP代码设置了SSE所需的HTTP头部,然后进入一个无限循环,每秒向客户端发送一次数据。数据以特定格式("data: "开头,双换行结束)发送,客户端可以接收并处理这些数据。

<?php

// 设置SSE头部

header('Content-Type: text/event-stream');

header('Cache-Control: no-cache');

header('Connection: keep-alive');

header('X-Accel-Buffering: no');

// 如果使用Nginx,这行很重要// 可选:确保脚本执行时间足够长(或者设置为0,不限时)

set_time_limit(0);

// 立即刷新输出缓冲区,确保头部和初始数据尽快发送

ob_implicit_flush(true);

ob_end_flush();

while (true) {

// 获取需要推送的数据(例如:从数据库、API、文件等) $currentTime = date('Y-m-d H:i:s'); $data = ['message' => 'Hello!', 'time' => $currentTime]; // 按照 SSE 格式输出数据 // "data:" 开头,后面跟JSON数据,两个换行符表示一个消息的结束 echo "data: " . json_encode($data) . "\n\n"; // 将输出缓冲区的内容立即发送到客户端 ob_flush(); flush(); // 暂停一段时间(例如每秒推送一次) sleep(1); // 在实际应用中,这里应该有终止循环的条件,例如检查连接是否断开

// if (connection_aborted()) break;}

?>

客户端 JavaScript 代码:

const eventSource = new EventSource('your_php_script.php');

eventSource.onmessage = function(event) {

const data = JSON.parse(event.data);

console.log('Received:', data.message, 'at', data.time);

};

eventSource.onerror = function(event) {

console.error('EventSource failed:', event);

};


完整代码实现

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Server-Sent Events (SSE) 完整示例</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d); color: #333; line-height: 1.6; padding: 20px; min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; } header { text-align: center; margin-bottom: 30px; color: white; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } h1 { font-size: 2.5rem; margin-bottom: 10px; } .subtitle { font-size: 1.2rem; opacity: 0.9; } .content { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } @media (max-width: 768px) { .content { grid-template-columns: 1fr; } } .card { background: white; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); padding: 25px; transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); } .card h2 { color: #1a2a6c; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #fdbb2d; } .code-container { background: #2d2d2d; color: #f8f8f2; border-radius: 5px; padding: 15px; overflow-x: auto; margin: 15px 0; font-family: 'Courier New', monospace; font-size: 14px; line-height: 1.4; } .btn { display: inline-block; background: #1a2a6c; color: white; border: none; padding: 12px 25px; border-radius: 5px; cursor: pointer; font-size: 16px; font-weight: bold; transition: all 0.3s ease; margin: 10px 5px; } .btn:hover { background: #fdbb2d; color: #333; transform: scale(1.05); } .btn-success { background: #28a745; } .btn-danger { background: #dc3545; } .events-container { max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 5px; padding: 15px; margin-top: 15px; background: #f9f9f9; } .event-item { padding: 10px; margin-bottom: 10px; border-radius: 5px; background: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); border-left: 4px solid #1a2a6c; } .event-item:nth-child(odd) { border-left-color: #fdbb2d; } .event-time { font-size: 0.8rem; color: #666; margin-bottom: 5px; } .event-data { font-weight: bold; } .status { padding: 10px; border-radius: 5px; margin: 15px 0; text-align: center; font-weight: bold; } .status.connected { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .status.disconnected { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .explanation { margin: 15px 0; padding: 15px; background: #e9f7fe; border-radius: 5px; border-left: 4px solid #1a2a6c; } .explanation h3 { color: #1a2a6c; margin-bottom: 10px; } footer { text-align: center; margin-top: 40px; color: white; opacity: 0.8; } </style></head><body> <div class="container"> <header> <h1>Server-Sent Events (SSE) 示例</h1> <p class="subtitle">实现服务器向客户端的实时数据推送</p> </header> <div class="content"> <div class="card"> <h2>服务器端代码 (PHP)</h2> <div class="code-container"> <pre>&lt;?php // 设置SSE头部 header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // 如果使用Nginx,这行很重要 // 可选:确保脚本执行时间足够长(或者设置为0,不限时) set_time_limit(0); // 立即刷新输出缓冲区 ob_implicit_flush(true); ob_end_flush(); // 设置计数器 $counter = 0; while (true) { // 每次循环增加计数器 $counter++; // 获取当前时间 $currentTime = date('Y-m-d H:i:s'); // 模拟一些数据 $data = [ 'message' => '这是服务器推送的消息 #' . $counter, 'time' => $currentTime, 'counter' => $counter, 'randomValue' => rand(1, 100) ]; // 按照 SSE 格式输出数据 echo "data: " . json_encode($data) . "\n\n"; // 将输出缓冲区的内容立即发送到客户端 ob_flush(); flush(); // 暂停1秒 sleep(1); // 在实际应用中,这里应该有终止循环的条件 // 例如检查连接是否断开: if (connection_aborted()) break; // 或者设置最大循环次数 if ($counter >= 50) { echo "data: " . json_encode(['message' => '连接已结束', 'time' => $currentTime]) . "\n\n"; ob_flush(); flush(); break; } } ?&gt;</pre> </div> <div class="explanation"> <h3>PHP代码说明</h3> <p>这段PHP代码设置了SSE所需的HTTP头部,然后进入一个无限循环,每秒向客户端发送一次数据。数据以特定格式("data: "开头,双换行结束)发送,客户端可以接收并处理这些数据。</p> </div> </div> <div class="card"> <h2>客户端演示</h2> <p>点击下面的按钮来模拟SSE连接:</p> <div class="button-group"> <button id="connectBtn" class="btn btn-success">连接SSE</button> <button id="disconnectBtn" class="btn btn-danger">断开连接</button> </div> <div id="status" class="status disconnected">未连接</div> <h3>接收的事件:</h3> <div id="eventsContainer" class="events-container"> <div class="event-item">等待连接...</div> </div> <div class="explanation"> <h3>客户端代码说明</h3> <p>客户端使用JavaScript的EventSource API连接到服务器端点。当接收到消息时,会解析JSON数据并显示在页面上。</p> </div> </div> </div> <div class="card"> <h2>客户端代码 (JavaScript)</h2> <div class="code-container"> <pre>// 创建EventSource对象连接到服务器 const eventSource = new EventSource('sse_server.php'); // 当接收到消息时 eventSource.onmessage = function(event) { const data = JSON.parse(event.data); console.log('接收到数据:', data); // 在页面上显示数据 displayEvent(data); }; // 当连接打开时 eventSource.onopen = function(event) { console.log('连接已建立'); updateStatus('connected'); }; // 当发生错误时 eventSource.onerror = function(event) { console.error('EventSource错误:', event); updateStatus('disconnected'); }; // 关闭连接的函数 function closeConnection() { eventSource.close(); updateStatus('disconnected'); } // 更新连接状态显示 function updateStatus(status) { const statusElement = document.getElementById('status'); statusElement.textContent = status === 'connected' ? '已连接' : '未连接'; statusElement.className = `status ${status}`; } // 显示接收到的数据 function displayEvent(data) { const eventsContainer = document.getElementById('eventsContainer'); const eventElement = document.createElement('div'); eventElement.className = 'event-item'; eventElement.innerHTML = ` <div class="event-time">${data.time}</div> <div class="event-data">${data.message}</div> <div>计数器: ${data.counter}, 随机值: ${data.randomValue}</div> `; eventsContainer.appendChild(eventElement); eventsContainer.scrollTop = eventsContainer.scrollHeight; }</pre> </div> </div> <footer> <p>Server-Sent Events (SSE) 示例 &copy; 2023</p> </footer> </div> <script> // 模拟SSE连接的变量 let eventSource = null; let eventInterval = null; let counter = 0; // 获取DOM元素 const connectBtn = document.getElementById('connectBtn'); const disconnectBtn = document.getElementById('disconnectBtn'); const statusElement = document.getElementById('status'); const eventsContainer = document.getElementById('eventsContainer'); // 连接SSE connectBtn.addEventListener('click', function() { if (eventSource) return; // 如果已经连接,则不再重复连接 updateStatus('connected'); eventsContainer.innerHTML = '<div class="event-item">连接已建立,开始接收数据...</div>'; // 模拟SSE连接 counter = 0; eventInterval = setInterval(simulateEvent, 1000); }); // 断开SSE连接 disconnectBtn.addEventListener('click', function() { if (eventInterval) { clearInterval(eventInterval); eventInterval = null; } updateStatus('disconnected'); eventsContainer.innerHTML += '<div class="event-item">连接已断开</div>'; }); // 模拟接收事件 function simulateEvent() { counter++; // 模拟数据 const currentTime = new Date().toLocaleString(); const data = { message: `这是服务器推送的消息 #${counter}`, time: currentTime, counter: counter, randomValue: Math.floor(Math.random() * 100) + 1 }; // 显示事件 displayEvent(data); // 模拟50次后自动停止 if (counter >= 50) { clearInterval(eventInterval); eventInterval = null; updateStatus('disconnected'); eventsContainer.innerHTML += '<div class="event-item">连接已结束</div>'; } } // 更新连接状态显示 function updateStatus(status) { statusElement.textContent = status === 'connected' ? '已连接' : '未连接'; statusElement.className = `status ${status}`; } // 显示接收到的数据 function displayEvent(data) { const eventElement = document.createElement('div'); eventElement.className = 'event-item'; eventElement.innerHTML = ` <div class="event-time">${data.time}</div> <div class="event-data">${data.message}</div> <div>计数器: ${data.counter}, 随机值: ${data.randomValue}</div> `; eventsContainer.appendChild(eventElement); eventsContainer.scrollTop = eventsContainer.scrollHeight; }

</script>

</body>

</html>

功能说明

这个实现包含以下部分:

  1. 服务器端PHP代码

    • 设置SSE所需的HTTP头部

    • 使用循环持续发送数据

    • 数据格式符合SSE规范

  2. 客户端JavaScript代码

    • 使用EventSource API连接服务器

    • 处理接收到的数据并显示在页面上

    • 提供连接和断开功能

  3. 模拟运行环境

    • 由于在线环境无法运行PHP,我们使用JavaScript模拟了SSE的行为

    • 点击"连接SSE"按钮开始接收模拟数据

    • 点击"断开连接"按钮停止接收数据

实际使用说明

在实际环境中,您需要:

  1. 将PHP代码保存为sse_server.php文件

  2. 将HTML/JavaScript代码保存为客户端文件

  3. 确保Web服务器支持PHP

  4. 通过Web服务器访问客户端文件


点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部