chrome对同一域名默认仅允许6个eventsource并发连接,超出的请求将pending排队;解决方案是复用连接(如sharedworker)或改用websocket。

多个标签页同时连接EventSource会怎样
Chrome 对同一域名最多只允许 6 个 EventSource 连接,超出的请求会排队或直接失败(状态码 pending),这不是 FrankenPHP 的限制,而是浏览器硬性策略。Firefox 和 Safari 行为类似,只是具体阈值略有差异。如果你在页面里无条件 new EventSource('/push.php'),每个新开标签页都会新建一个连接,很快就会触达上限,后续标签页收不到事件,控制台也看不到明显报错——只有 Network 面板里请求一直 pending。
怎么避免多个标签页互相干扰
核心思路是「复用连接」或「区分实例」,而不是放任每个标签页独立建连:
- 前端优先用
localStorage+postMessage实现主从标签页通信:仅第一个标签页创建EventSource,其余通过window.addEventListener('storage')监听共享状态变更 - 若必须每个标签页都连(如需独立用户上下文),务必加唯一标识参数:
new EventSource('/push.php?uid=' + userId),这样后端可按$_GET['uid']区分会话,也方便在日志或限流中识别来源 - 不要依赖浏览器默认重连:多个标签页同时触发重连,可能在几秒内发起数十次请求,Caddy 的 Mercure 默认限速是 10 次/秒,超限直接返回
429 Too Many Requests - 前端主动控制生命周期:监听
visibilitychange,页面切到后台时调用eventSource.close(),切回前台再重建(注意防抖,避免频繁开关)
服务端如何配合多标签场景
FrankenPHP 的 SSE 脚本本身不感知客户端数量,但你可以通过请求参数、会话或 JWT 声明做轻量级分流:
-
push.php中检查$_GET['uid']或$_SERVER['HTTP_AUTHORIZATION'],只推送该用户相关事件,避免广播式浪费 - 用
connection_aborted()判断连接是否已断,及时 break 循环释放 worker 资源;否则一个卡住的标签页可能长期占用一个 PHP worker - 如果走 Mercure,主题 URL 必须带用户维度,例如
https://example.com/users/123/notifications,而不是泛化的/notifications;否则所有订阅者都会收到同一条消息 - 不要在脚本里用
sleep(1)等待,真实场景建议监听信号或使用stream_select()等非阻塞方式,否则多个连接会把 worker 全部占满
调试时容易忽略的细节
多个标签页下问题难复现,关键要确认三件事是否一致:
- 所有标签页访问的是**完全相同的 URL**(协议、域名、路径、查询参数),
https://a.com/push.php和https://a.com/push.php?是两个不同源,各自计数 - 前端
EventSource实例没有被意外重复创建(比如 Vue 组件多次 mounted 未销毁旧实例) - Caddy 日志里是否出现大量
context deadline exceeded:说明 worker 队列已满,不是连接数问题,而是 PHP 层处理不过来,得看frankenphp_queue_depth指标
真正麻烦的从来不是“连不上”,而是“连上了但没反应”——这时候大概率是某一个标签页悄悄维持着一个卡死的连接,把整个 worker 拖慢了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











