Server-Sent Events连接为何循环触发open事件?如何解决?
问题解答:SSE客户端持续重连的原因与服务器过载风险解决
你的担忧是正确的
当服务端主动结束SSE连接(比如有限循环执行完毕),EventSource的默认行为会自动重试连接(默认间隔3秒)。大量客户端不断发起新的连接请求,会快速消耗服务器的连接数、进程/线程资源,尤其在高并发场景下,极易导致服务器过载,出现响应变慢、连接失败甚至服务崩溃的情况。
问题根源
你修改后的服务端代码会在循环结束后正常关闭HTTP连接,而EventSource规范设计时就包含了「连接意外关闭时自动重连」的机制,用于应对网络波动、服务端临时故障等场景。但你的场景中是服务端主动结束连接,不属于意外情况,却触发了默认的重连逻辑,导致客户端不断建立新连接。
解决方法
1. 服务端发送明确的结束标记(推荐)
根据SSE规范,当服务端发送data: [DONE](或自定义的结束标识)并结束响应后,客户端收到该标记会自动停止重连。
修改后的服务端代码:
<?php header("Cache-Control: no-store"); header("Content-Type: text/event-stream"); $counter = rand(1, 10); for ($x = 0; $x < 5; $x++) { // 发送ping事件 echo "event: ping\n"; $curDate = date(DATE_ISO8601); echo 'data: {"time": "' . $curDate . '"}'; echo "\n\n"; $counter--; if (!$counter) { echo 'data: This is a message at time ' . $curDate . "\n\n"; $counter = rand(1, 10); } ob_end_flush(); flush(); // 检测客户端是否断开连接 if (connection_aborted()) break; sleep(1); } // 发送结束标记,通知客户端停止重连 echo "data: [DONE]\n\n"; ob_end_flush(); flush(); ?>
2. 客户端主动关闭连接
在客户端监听消息事件,收到服务端的结束标记后,手动调用evtSource.close()终止连接,避免自动重连。
修改后的客户端代码:
var evtSource = new EventSource('http://localhost/ssevent.php'); // 监听ping事件 evtSource.addEventListener('ping', function(e) { console.log(e.data); }, false); // 监听普通消息,处理结束标记 evtSource.addEventListener('message', function(e) { if (e.data === '[DONE]') { evtSource.close(); console.log("Connection closed intentionally."); return; } console.log(e.data); }, false); evtSource.addEventListener('open', function(e) { console.log("Connection was opened."); }, false); evtSource.addEventListener('error', function(e) { if (e.readyState === EventSource.CLOSED) { console.log("Error, connection was closed."); } }, false);
3. 优化长连接场景(如果需要持续推送)
如果你的业务需要持续的SSE推送,不要用有限循环,而是保留while(true),但要优化资源占用:
- 定期检测客户端连接状态(通过
connection_aborted()),及时关闭僵尸连接 - 使用异步PHP框架(如Swoole、ReactPHP)代替传统PHP-FPM,这类框架可以用单进程/线程处理数千个长连接,避免PHP-FPM进程耗尽的问题
内容的提问来源于stack exchange,提问作者warriorforce
相关产品推荐
相关产品推荐

