前言

随着ChatGPT等聊天机器人和实时通信应用的流行,Server-Sent Events (SSE) 作为一种简单而有效的技术,用于实现服务器向客户端异步推送数据,变得越来越重要。

Server-Sent Events (SSE) 是一种允许服务器向客户端推送实时更新的技术,而无需客户端发起请求。这种模式非常适合实时通知、实时数据更新等场景。在 Laravel 这个强大的 PHP 框架中实现 SSE 非常直接且高效。下面,我将使用Laravel完成一个简单的 SSE 示例。

创建 SSE 路由和控制器

首先,我们需要为 SSE 创建一个路由和对应的控制器方法。

步骤 1: 添加路由

打开 routes/web.php 文件,添加一个新的路由指向我们即将创建的控制器方法:

Route::get('/sse', [\App\Http\Controllers\SseController::class, 'sendEvents'])->name('sse');

步骤 2: 创建控制器

运行以下命令来生成一个新的控制器:

php artisan make:controller SseController

接着,在新生成的 app/Http/Controllers/SseController.php 文件中添加 sendEvents 方法:
为了让 SSE 控制器响应由 Laravel 事件系统触发的消息,这里使用 Redis 的发布订阅模式(Pub/Sub)来获取事件消息。

// app/Http/Controllers/SseController.php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;

public function sendEvents()
{
    $response = response()->stream(function () {
        // 初始化响应头
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache');
        header('Connection: keep-alive');
        
        Redis::subscribe(['message-channel'], function ($message) {
            // 当有新消息发布到'message-channel'时,此回调会被触发
            $data = json_decode($message->payload, true);
            echo "data: " . json_encode($data) . "\n\n";
            ob_flush();
            flush();
        });

        // 保持连接开启,以便持续监听
        while (true) {
            // 可以添加超时处理逻辑,避免空转
            sleep(1);
        }
    });

    return $response;
}

创建事件和监听器

步骤 1: 定义事件

首先,创建一个新的事件类。在终端运行以下命令:

php artisan make:event NewMessageEvent

这会在 app\Events 目录下创建 NewMessageEvent.php 文件。打开它,并定义事件承载的数据,比如

// app\Events\NewMessageEvent.php

namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class NewMessageEvent
{
    use Dispatchable, SerializesModels;

    public $message;

    /**
     * Create a new event instance.
     *
     * @param string $message
     */
    public function __construct(string $message)
    {
        $this->message = $message;
    }
}

步骤 2: 创建监听器

接下来,创建一个监听器来响应这个事件。在终端执行

php artisan make:listener SendMessageToSSE --event=NewMessageEvent

编辑新生成的 app\Listeners\SendMessageToSSE.php 文件,使其在事件触发时向 SSE 连接的客户端发送消息:

// app\Listeners\SendMessageToSSE.php

namespace App\Listeners;

use App\Events\NewMessageEvent;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\DB; // 如果需要访问数据库等

class SendMessageToSSE
{
    use InteractsWithQueue, SerializesModels;

    /**
     * Handle the event.
     *
     * @param  \App\Events\NewMessageEvent  $event
     * @return void
     */
    public function handle(NewMessageEvent $event)
    {
        // 这里可以添加逻辑判断是否需要推送消息
        // 例如检查是否有客户端连接、过滤消息内容等
		// 发布消息到Redis的频道,例如'messages-channel'
    	Redis::publish('messages-channel', json_encode([
        	'message' => $message,
    	]));
}

步骤 3: 注册事件监听器

app\Providers\EventServiceProvider.php 文件的 $listen 属性中注册事件监听器

protected $listen = [
    NewMessageEvent::class => [
        SendMessageToSSE::class,
    ],
];

步骤 4: 触发事件

在你的业务逻辑中,当需要推送消息时,简单地触发 NewMessageEvent 事件:

event(new NewMessageEvent('Hello from Laravel!'));

这样,每当在你的应用中触发 NewMessageEvent,通过 Redis 订阅机制,SSE 控制器就会接收到消息,并将其推送给所有已连接的客户端。

前端实现

前端使用JavaScript的EventSource来连接这个SSE流:

const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
    console.log(event.data);
};

这样,每当服务器端发送消息时,前端的onmessage事件处理器就会被触发,并且可以在其中处理接收到的数据。