大家好,我是Tinywan。今天我们来聊一聊 SSE(Server-Sent Events,服务器发送事件),这是一种非常适合实时数据推送的技术。与 WebSocket 不同,SSE 只支持服务器到客户端的单向通信。
SSE 的核心是建立一个持久化的 HTTP 长连接,让服务器可以随时向客户端推送数据,而无需客户端频繁发起请求。它是一种 “服务器到客户端” 的单向通信模式,与 WebSocket 的双向通信形成互补。
其优势在于原生支持自动重连、API 简单(EventSource接口)、服务器实现成本低,适合无需客户端回传数据的场景。与 WebSocket 相比,SSE 更专注于单向推送,实现和维护成本更低,是实时通知、日志展示等场景的理想选择。

本质上,它采用流式方法实现,客户端向服务器发起连接请求并保持连接打开。然后服务器主动向客户端推送消息。使用服务器发送事件(SSE)从服务器发送到客户端的数据必须是 UTF-8 编码的,返回的内容类型是 text/event-stream 。
例如,在使用 ChatGPT 时,当你向它提问时,你会看到它逐字显示答案。实际上,ChatGPT 在计算的同时主动“推送”预计算数据给你,利用 SSE 技术边计算边返回数据,从而避免了可能导致直接页面关闭的长时间接口等待。
这里客户端代码为了解决访问跨域问题,直接使用webman视图完成客户端页面。模板是使用的:think-template。
如何使用可以看这里哦!
https://www.workerman.net/doc/webman/view.html
<?php
/**
* @desc 控制器中使用SSE
* @author Tinywan(ShaoBo Wan)
*/
declare(strict_types=1);
namespace app\controller;
use support\Request;
use support\Response;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\ServerSentEvents;
use Workerman\Timer;
class StreamController
{
/**
* @desc client
* @param Request $request
* @return Response
* @author Tinywan(ShaoBo Wan)
*/
publicfunction client(Request $request): Response
{
return view('stream/client', ['name' => '开源技术小栈']);
}
/**
* @desc server
* @param Request $request
* @return Response
* @author Tinywan(ShaoBo Wan)
*/
publicfunction server(Request $request): Response
{
$connection = $request->connection;
$id = Timer::add(1, function () use ($connection, &$id) {
if ($connection->getStatus() !== TcpConnection::STATUS_ESTABLISHED) {
Timer::del($id);
}
$connection->send(new ServerSentEvents([
'event' => 'message',
'data' => date('Y-m-d H:i:s').' 服务端发送数据',
'id' => 2026
]));
});
$header = [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'Connection' => 'keep-alive',
];
return response('', 200, $header);
}
}
Step1. 必须设置的 HTTP 响应头
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
可选但强烈推荐:
X-Accel-Buffering: no # 关闭 Nginx 等代理的缓冲(非常重要)
Step2. SSE 消息的基本格式规则
每条消息由若干字段组成,每个字段独占一行,字段之间用换行符分隔,整条消息以两个连续换行符(\n\n)结束。
字段写法 | 作用说明 | 是否必须 | 示例 | 客户端如何获取 |
|---|---|---|---|---|
data: | 消息正文内容(最核心字段) | 必须 | data: {"text": "Hello"}\n | event.data |
event: | 自定义事件类型(不写默认为 message) | 可选 | event: token\n | addEventListener('token', ...) |
id: | 消息唯一标识,用于断线重连续传 | 推荐 | id: 12345\n | event.lastEventId |
retry: | 建议客户端重连间隔(毫秒),断开后客户端会参考此值自动重连 | 可选 | retry: 5000\n | 自动处理 |
: (冒号+空格) | 注释行,客户端会完全忽略,常用于调试、心跳、日志 | 可选 | : 这是一条调试信息\n | 忽略 |
Step3. 数据推送:基于规范的文本流格式
服务器通过 HTTP 长连接持续向客户端发送 “事件”,每个事件需遵循严格的文本格式,以\n\n作为结束符。
格式 1:默认事件(客户端通过 onmessage 接收)
data: 您有一条新消息\n
data: 内容:Hello World\n\n # 多行 data 会合并为一条消息
格式 2:自定义事件名(客户端通过 addEventListener 接收)
event: orderStatus # 事件名(如“订单状态更新”)
id: 10086 # 事件 ID(用于重连时定位断点)
data: 订单已发货\n\n
格式 3:心跳保活(客户端忽略,仅维持连接)
: 这是注释,无实际数据,防止长连接超时\n\n
客户端请求
// 客户端创建 SSE 连接(仅支持 GET 方法)
const sse = new EventSource('/service/stream');
浏览器会自动发送 HTTP 请求,携带特殊头信息(如:Accept: text/event-stream)。
全部伪代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>客户端 SSE 输出</title>
<style>
body {
font-family: system-ui, sans-serif;
margin: 20px;
background: #fafafa;
}
#output {
white-space: pre-wrap;
word-wrap: break-word;
background: white;
border: 1px solid #ddd;
padding: 16px;
min-height: 80px;
line-height: 1.5;
font-family: Consolas, "Courier New", monospace;
overflow-y: auto;
}
</style>
</head>
<body>
<h3>客户端 SSE 输出</h3>
<div id="output"> SSE 连接中...</div>
<script>
const output = document.getElementById('output');
const es = new EventSource('http://127.0.0.1:8787/stream/server');
es.onmessage = function(e) {
output.textContent += e.data + '\n';
output.scrollTop = output.scrollHeight;
};
es.onopen = function() {
output.textContent = ''; // 清空“连接中...”文字
};
</script>
</body>
</html>
客户端:http://127.0.0.1:8787/stream/client
输出页面内容如下所示:

SSE 最适合服务器主动、单向、实时推送数据的场景,尤其在不需要客户端频繁上行、实现简单、资源占用低的情况下表现出色。以下是主要实际应用场景: