编程 ThinkPHP SSE技术解析:实现服务器实时事件推送

2025-09-19 15:22:12 +0800 CST views 7

ThinkPHP SSE技术解析:实现服务器实时事件推送

概述

在现代Web应用开发中,实时通信已成为提升用户体验的关键技术。Server-Sent Events(SSE)作为一种轻量级的服务器推送技术,为实现单向实时通信提供了简单有效的解决方案。本文将详细介绍如何在ThinkPHP框架中实现SSE功能,包括后端控制器的编写和前端页面的交互。

SSE技术简介

SSE是一种允许服务器向客户端发送实时更新的HTML5技术。与WebSocket相比,SSE更简单易用,特别适合只需要服务器向客户端单向推送数据的场景,如实时通知、新闻推送、进度更新等。

SSE的主要特点包括:

  • 基于HTTP协议,无需特殊协议
  • 自动重连机制
  • 简单易用的API
  • 内置支持事件类型区分

ThinkPHP后端实现

控制器设计

在ThinkPHP中实现SSE需要创建一个专门的控制器处理事件流请求:

<?php
declare (strict_types = 1);

namespace app\gbi\controller;

use app\BaseController;
use think\Request;

class Sse extends BaseController
{
    public function search(Request $request)
    {
        // 设置SSE所需的HTTP头
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        header('Access-Control-Allow-Origin: *');
        
        // 防止脚本超时
        set_time_limit(0);
        
        // 获取客户端传递的lastEventId
        $lastEventId = $request->header('Last-Event-ID');
        
        // 发送初始注释消息
        echo ": SSE连接已建立\n\n";
        ob_flush();
        flush();
        
        $eventId = $lastEventId;
        $counter = 0;
        
        // 模拟持续发送事件
        while (true) {
            // 检查客户端是否仍然连接
            if (connection_aborted()) {
                break;
            }
            
            // 生成事件数据
            $eventId++;
            $data = [
                'message' => '服务器时间: ' . date('Y-m-d H:i:s'),
                'time' => date('H:i:s'),
                'counter' => $counter
            ];
            
            // 根据计数器的值发送不同类型的事件
            if ($counter % 10 == 0) {
                $this->sendEvent($eventId, 'notification', [
                    'message' => '系统通知 #' . $counter,
                    'priority' => 'medium',
                    'time' => date('H:i:s')
                ]);
            } elseif ($counter % 7 == 0) {
                $this->sendEvent($eventId, 'alert', [
                    'message' => '系统警报 #' . $counter,
                    'level' => 'warning',
                    'time' => date('H:i:s')
                ]);
            } elseif ($counter % 5 == 0) {
                $this->sendEvent($eventId, 'update', [
                    'message' => '数据已更新 #' . $counter,
                    'id' => rand(1000, 9999),
                    'time' => date('H:i:s')
                ]);
            } else {
                // 发送普通消息事件
                $this->sendEvent($eventId, 'message', $data);
            }
            
            $counter++;
            sleep(1); // 休眠1秒
        }
        
        exit;
    }
    
    /**
     * 发送SSE事件
     */
    private function sendEvent($id, $event, $data)
    {
        echo "id: {$id}\n";
        
        if ($event && $event != 'message') {
            echo "event: {$event}\n";
        }
        
        echo "data: " . json_encode($data) . "\n\n";
        ob_flush();
        flush();
    }
}

关键技术点解析

  1. HTTP头设置:必须设置正确的HTTP头以确保浏览器正确识别SSE流
  2. 无限循环:使用while(true)保持连接持续开放
  3. 连接状态检查:通过connection_aborted()检测客户端是否断开连接
  4. 输出缓冲处理:使用ob_flush()和flush()确保数据立即发送到客户端
  5. 事件格式:遵循SSE规范的事件格式,包括id、event和data字段

前端实现

页面结构与样式

前端页面采用Bootstrap框架构建,包含连接控制、事件日志和说明三个主要区域:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ThinkPHP SSE 示例</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* 样式代码略,详见原始内容 */
    </style>
</head>
<body>
    <div class="container">
        <h1 class="text-center mb-4">ThinkPHP 服务器推送事件(SSE)示例</h1>
        
        <!-- 连接控制卡片 -->
        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">连接控制</h5>
            </div>
            <div class="card-body">
                <!-- 连接状态和控制按钮 -->
                <div class="d-flex justify-content-between align-items-center mb-3">
                    <div>
                        <span id="statusIndicator" class="status-indicator status-disconnected"></span>
                        <span id="statusText">未连接</span>
                    </div>
                    <div>
                        <button id="connectBtn" class="btn btn-success btn-action">连接</button>
                        <button id="disconnectBtn" class="btn btn-danger btn-action" disabled>断开</button>
                    </div>
                </div>
                
                <!-- 事件类型选择 -->
                <div class="mb-3">
                    <label for="eventType" class="form-label">事件类型</label>
                    <select class="form-select" id="eventType">
                        <option value="message">普通消息</option>
                        <option value="notification">通知</option>
                        <option value="alert">警报</option>
                        <option value="update">数据更新</option>
                    </select>
                </div>
                
                <!-- 自定义消息输入 -->
                <div class="mb-3">
                    <label for="customMessage" class="form-label">自定义消息(可选)</label>
                    <input type="text" class="form-control" id="customMessage" placeholder="输入自定义消息内容">
                </div>
                
                <button id="sendEventBtn" class="btn btn-primary" disabled>发送事件</button>
            </div>
        </div>
        
        <!-- 事件日志卡片 -->
        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">事件日志</h5>
            </div>
            <div class="card-body">
                <div id="eventLog" class="event-log"></div>
            </div>
        </div>
        
        <!-- 说明卡片 -->
        <div class="card">
            <div class="card-header">
                <h5 class="mb-0">说明</h5>
            </div>
            <div class="card-body">
                <p>这是一个使用ThinkPHP实现服务器推送事件(SSE)的示例。</p>
                <!-- 详细说明略 -->
            </div>
        </div>
    </div>
    
    <script>
        // JavaScript代码详见下一节
    </script>
</body>
</html>

JavaScript交互逻辑

前端JavaScript代码负责处理SSE连接的建立、关闭和事件处理:

document.addEventListener('DOMContentLoaded', function() {
    const eventLog = document.getElementById('eventLog');
    const connectBtn = document.getElementById('connectBtn');
    const disconnectBtn = document.getElementById('disconnectBtn');
    const sendEventBtn = document.getElementById('sendEventBtn');
    const statusIndicator = document.getElementById('statusIndicator');
    const statusText = document.getElementById('statusText');
    const eventTypeSelect = document.getElementById('eventType');
    const customMessageInput = document.getElementById('customMessage');
    
    let eventSource = null;
    
    // 添加日志条目
    function addLogEntry(message, type = 'info') {
        const now = new Date();
        const timestamp = now.toLocaleTimeString();
        const logEntry = document.createElement('div');
        logEntry.className = `event-item text-${type}`;
        logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
        eventLog.appendChild(logEntry);
        eventLog.scrollTop = eventLog.scrollHeight;
    }
    
    // 建立SSE连接
    function connectSSE() {
        if (eventSource) {
            return;
        }
        
        try {
            // 创建EventSource对象连接到SSE端点
            eventSource = new EventSource('/gbi/sse/search');
            
            eventSource.onopen = function() {
                statusIndicator.className = 'status-indicator status-connected';
                statusText.textContent = '已连接';
                connectBtn.disabled = true;
                disconnectBtn.disabled = false;
                sendEventBtn.disabled = false;
                addLogEntry('SSE连接已建立', 'success');
            };
            
            // 处理通用消息事件
            eventSource.onmessage = function(event) {
                const data = JSON.parse(event.data);
                addLogEntry(`消息: ${data.message} (时间: ${data.time})`);
            };
            
            // 处理特定类型的事件
            eventSource.addEventListener('notification', function(event) {
                const data = JSON.parse(event.data);
                addLogEntry(`通知: ${data.message} (优先级: ${data.priority})`, 'warning');
            });
            
            eventSource.addEventListener('alert', function(event) {
                const data = JSON.parse(event.data);
                addLogEntry(`警报: ${data.message} (级别: ${data.level})`, 'danger');
            });
            
            eventSource.addEventListener('update', function(event) {
                const data = JSON.parse(event.data);
                addLogEntry(`数据更新: ${data.message} (ID: ${data.id})`, 'info');
            });
            
            eventSource.onerror = function() {
                addLogEntry('SSE连接错误', 'danger');
                disconnectSSE();
            };
        } catch (error) {
            addLogEntry(`连接失败: ${error.message}`, 'danger');
        }
    }
    
    // 断开SSE连接
    function disconnectSSE() {
        if (eventSource) {
            eventSource.close();
            eventSource = null;
        }
        
        statusIndicator.className = 'status-indicator status-disconnected';
        statusText.textContent = '未连接';
        connectBtn.disabled = false;
        disconnectBtn.disabled = true;
        sendEventBtn.disabled = true;
        addLogEntry('SSE连接已关闭', 'info');
    }
    
    // 发送自定义事件(模拟)
    function sendCustomEvent() {
        if (!eventSource) {
            addLogEntry('请先建立SSE连接', 'warning');
            return;
        }
        
        const eventType = eventTypeSelect.value;
        const customMessage = customMessageInput.value || `这是一条${eventType}类型的测试消息`;
        
        addLogEntry(`已请求发送事件: ${eventType} - ${customMessage}`, 'info');
        
        // 模拟服务器响应
        setTimeout(() => {
            const events = {
                message: { message: customMessage, time: new Date().toLocaleTimeString() },
                notification: { message: customMessage, priority: 'high' },
                alert: { message: customMessage, level: 'warning' },
                update: { message: customMessage, id: Math.floor(Math.random() * 1000) }
            };
            
            const event = new MessageEvent(eventType, {
                data: JSON.stringify(events[eventType])
            });
            
            if (eventType === 'message') {
                eventSource.onmessage(event);
            } else {
                eventSource.dispatchEvent(event);
            }
        }, 500);
    }
    
    // 绑定按钮事件
    connectBtn.addEventListener('click', connectSSE);
    disconnectBtn.addEventListener('click', disconnectSSE);
    sendEventBtn.addEventListener('click', sendCustomEvent);
    
    // 初始化日志
    addLogEntry('页面已加载,点击"连接"按钮开始SSE通信。');
});

实际应用中的注意事项

  1. 性能考虑:SSE连接会保持较长时间,需要合理管理服务器资源
  2. 错误处理:实现完整的错误处理和重连机制
  3. 安全性:对敏感数据进行适当加密和验证
  4. 跨域问题:生产环境中需要正确配置CORS策略
  5. 负载均衡:在集群环境中需要确保SSE连接与正确服务器保持关联

扩展应用场景

SSE技术可以应用于多种场景:

  1. 实时通知系统:向用户推送重要通知和更新
  2. 实时数据监控:监控系统状态并实时推送变化
  3. 进度跟踪:长时间操作的任务进度更新
  4. 实时聊天应用:简单的一对多或广播式聊天
  5. 实时数据可视化:动态更新图表和数据展示

总结

本文详细介绍了在ThinkPHP框架中实现SSE技术的完整方案,包括后端控制器的编写和前端页面的交互。通过SSE,我们可以轻松实现服务器向客户端的实时事件推送,提升Web应用的用户体验。

SSE作为一种轻量级的实时通信方案,在不需要双向通信的场景下比WebSocket更加简单实用。结合ThinkPHP框架的强大功能,开发者可以快速构建出功能丰富的实时应用。

需要注意的是,在生产环境中使用SSE时,还应考虑连接管理、错误处理、安全性等更多因素,以确保系统的稳定性和可靠性。

复制全文 生成海报 Web开发 实时通信 PHP框架

推荐文章

使用 Go Embed
2024-11-19 02:54:20 +0800 CST
Vue3中哪些API被废弃了?
2024-11-17 04:17:22 +0800 CST
JavaScript设计模式:组合模式
2024-11-18 11:14:46 +0800 CST
为什么要放弃UUID作为MySQL主键?
2024-11-18 23:33:07 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
20个超实用的CSS动画库
2024-11-18 07:23:12 +0800 CST
一个简单的打字机效果的实现
2024-11-19 04:47:27 +0800 CST
mysql删除重复数据
2024-11-19 03:19:52 +0800 CST
php curl并发代码
2024-11-18 01:45:03 +0800 CST
JavaScript设计模式:装饰器模式
2024-11-19 06:05:51 +0800 CST
Python 基于 SSE 实现流式模式
2025-02-16 17:21:01 +0800 CST
在 Nginx 中保存并记录 POST 数据
2024-11-19 06:54:06 +0800 CST
如何将TypeScript与Vue3结合使用
2024-11-19 01:47:20 +0800 CST
程序员茄子在线接单