FrankenPHP 本身不直接提供多用户推送测试工具,但其 Mercure Hub 和 Worker 模式天然支持并发订阅与广播;可通过浏览器标签页模拟多订阅者,或用 curl 并发请求压测 Hub 分发能力,亦可借助 Worker 手动管理连接池实现 WebSocket 多用户推送。

FrankenPHP 本身不直接提供“多用户推送测试工具”,但它的 Mercure Hub 和 Worker 模式天然支持并发订阅与广播。测试重点不是“能不能推”,而是验证 Hub 是否正确分发、客户端是否稳定维持连接、以及你的发布逻辑是否可扩展。
用浏览器标签页模拟多个用户订阅 Mercure
Mercure 基于 EventSource,每个 new EventSource() 实例就是一个独立订阅者。你不需要启动多个服务或写脚本,直接开 5–10 个浏览器标签页就能压测基础链路:
- 确保 Caddyfile 中已启用
mercure指令块,并配置了anonymous(允许未认证订阅) - 在每个标签页中执行相同 JS:
const es = new EventSource('https://localhost:8080/.well-known/mercure?topic=https://example.com/chat');es.onmessage = e => console.log('Received:', e.data);es.onerror = e => console.error('ES error', e); - 用
mercure_publish()或 cURL 向同一 topic 发一条消息,观察所有标签页是否几乎同时收到 - 注意:Chrome 对单域名 EventSource 连接数有默认限制(通常为 6),若开太多标签页会触发排队或失败,这不是 Mercure 问题,而是浏览器策略
用 curl 并发请求模拟百级订阅者
当需要更可控、更高密度的测试时,curl + shell 并发比浏览器更可靠,且能绕过浏览器连接限制:
- 先确认 Mercure 的订阅端点可公开访问(例如
https://localhost:8080/.well-known/mercure?topic=https://acme.com/msg) - 运行以下命令启动 50 个后台订阅进程(每条只收一条后退出):
for i in $(seq 1 50); do curl -N "https://localhost:8080/.well-known/mercure?topic=https://acme.com/msg" --max-time 10 & done
- 另起终端,用
mercure_publish()或 POST 向该 topic 发布消息,观察各curl进程是否在几毫秒内打印响应 - 关键点:
-N禁用缓冲,--max-time 10防止挂死;若大量curl卡住,说明 Hub 已达连接上限或 TLS 握手阻塞(需检查 Go 运行时GOMAXPROCS或系统文件描述符限制)
Worker 模式下推送需手动管理连接生命周期
如果你没走 Mercure,而是用 Worker + WebSocket 自建推送(如聊天室),那“多用户接收”本质是 PHP 脚本维护一个连接池并轮询/广播——这里最容易出错的是内存泄漏和连接假死:
立即学习“PHP免费学习笔记(深入)”;
- 不要在 Worker 循环里无条件
array_push($connections, $fd),必须配合stream_select()或frankenphp_handle_request()的返回值判断连接状态 - 每次广播前,先遍历连接列表,用
feof($fd) || !is_resource($fd)过滤掉已断开的句柄 - 测试时用
netstat -an | grep :8080 | wc -l查看实际 ESTABLISHED 连接数,对比你预期的“在线用户数”,差值过大说明有连接未被正确清理 - Worker 脚本里必须调用
gc_collect_cycles(),否则长连接场景下 PHP 内存占用会随时间线性上涨(FrankenPHP 不自动触发 GC)
真正卡住多数人的地方,不是“怎么发”,而是“谁来判定用户还在线”。Mercure 依赖客户端心跳保活,WebSocket 则完全由你控制 ping/pong —— 两者都要求你在代码里显式处理超时,而不是指望框架自动兜底。



















