本文目录导读:

PHP 服务端事件(Server-Sent Events, SSE)是一种允许服务器主动向客户端推送数据的技术,在 PHP 中实现 SSE 相对简单,主要通过设置响应头并不断输出特定格式的数据来完成。
基本实现原理
服务器端 PHP 脚本
<?php
// 设置 SSE 需要的响应头
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 禁用输出缓冲
@ob_end_flush();
ob_implicit_flush(true);
// 无限循环发送事件
while (true) {
// 获取当前时间
$time = date('Y-m-d H:i:s');
// 发送事件
echo "data: 当前时间: {$time}\n\n";
flush();
// 每 5 秒发送一次
sleep(5);
// 检测客户端是否断开连接
if (connection_aborted()) {
break;
}
}
?>
客户端 JavaScript 代码
<!DOCTYPE html>
<html>
<head>SSE Demo</title>
</head>
<body>
<div id="events"></div>
<script>
// 创建 EventSource 连接
const eventSource = new EventSource('sse.php');
// 监听消息事件
eventSource.onmessage = function(event) {
const eventsDiv = document.getElementById('events');
const p = document.createElement('p');
p.textContent = `收到: ${event.data}`;
eventsDiv.appendChild(p);
};
// 监听错误
eventSource.onerror = function(event) {
console.error('SSE 连接错误:', event);
};
// 关闭连接的函数
function closeConnection() {
eventSource.close();
console.log('连接已关闭');
}
</script>
</body>
</html>
高级功能实现
带事件类型的 SSE
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 发送不同事件类型
echo "event: userUpdate\n";
echo "data: {\"username\": \"john\", \"action\": \"login\"}\n\n";
flush();
echo "event: notification\n";
echo "id: 123\n"; // 可选的事件ID
echo "data: 你有新的消息\n\n";
flush();
客户端监听特定事件:
const eventSource = new EventSource('events.php');
eventSource.addEventListener('userUpdate', function(e) {
console.log('用户更新:', JSON.parse(e.data));
});
eventSource.addEventListener('notification', function(e) {
console.log('通知:', e.data);
});
带重连机制的 SSE
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 设置重连时间(毫秒)
echo "retry: 3000\n\n";
$counter = 0;
while (true) {
$counter++;
// 发送带 ID 的事件,确保客户端能跟踪
echo "id: {$counter}\n";
echo "data: 消息 #{$counter}\n\n";
flush();
if (connection_aborted()) {
break;
}
sleep(2);
}
?>
从数据库推送事件
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// 数据库连接
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$lastCheck = time();
while (true) {
// 检查是否有新数据
$stmt = $pdo->prepare("SELECT * FROM notifications WHERE created_at > :last_time");
$stmt->execute([':last_time' => date('Y-m-d H:i:s', $lastCheck)]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "event: {$row['type']}\n";
echo "data: " . json_encode($row) . "\n\n";
flush();
}
$lastCheck = time();
if (connection_aborted()) {
break;
}
sleep(2); // 每 2 秒检查一次
}
?>
重要注意事项
PHP 配置要求
; 需要在 php.ini 中设置 max_execution_time = 0 ; 取消执行时间限制 output_buffering = Off ; 关闭输出缓冲
服务器配置
Apache:
# 启用 mod_headers 模块 Header set Cache-Control "no-cache"
Nginx:
# 禁用代理缓冲 proxy_buffering off; proxy_cache off; fastcgi_buffering off;
性能优化建议
<?php
// 使用数据库连接池或持久连接
// 实现心跳机制防止超时
while (true) {
// 发送心跳
echo ": heartbeat\n\n";
flush();
// 实际业务逻辑
// ...
// 控制频率
usleep(500000); // 0.5秒
}
?>
完整示例:实时消息通知系统
服务器端 (notifications.php)
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
// 身份验证(示例)
$user_id = $_GET['user_id'] ?? die('User ID required');
// 建立与 Redis 的连接
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 订阅用户的通知频道
$channel = "user:{$user_id}:notifications";
while (true) {
// 从 Redis 获取新消息
$message = $redis->blPop([$channel], 2);
if ($message) {
$data = json_decode($message[1], true);
echo "event: {$data['type']}\n";
echo "data: " . json_encode([
'title' => $data['title'],
'message' => $data['message'],
'timestamp' => time()
]) . "\n\n";
flush();
}
// 发送心跳
echo ": keepalive\n\n";
flush();
if (connection_aborted()) {
break;
}
}
?>
客户端 (index.html)
<!DOCTYPE html>
<html>
<head>实时通知</title>
</head>
<body>
<div id="notifications"></div>
<script>
const userId = 1; // 当前用户ID
const eventSource = new EventSource(`notifications.php?user_id=${userId}`);
eventSource.addEventListener('message', function(e) {
const notification = JSON.parse(e.data);
showNotification(notification);
});
eventSource.addEventListener('alert', function(e) {
const alert = JSON.parse(e.data);
showAlert(alert);
});
function showNotification(data) {
const div = document.getElementById('notifications');
const p = document.createElement('p');
p.textContent = `[${data.title}] ${data.message}`;
div.appendChild(p);
}
function showAlert(data) {
alert(`紧急通知: ${data.message}`);
}
eventSource.onerror = function() {
console.log('尝试重新连接...');
};
</script>
</body>
</html>
SSE 比 WebSocket 更适合简单的服务器推送场景,实现更简单,且原生支持 HTTP 协议,但请注意它只支持单向通信(服务器→客户端),且连接数较多时对服务器资源消耗较大。