FrankenPHP下长轮询频繁断开主因是默认60秒超时,需同步配置frankenphp.router.timeout和frankenphp.worker.timeout为一致值(如300秒),并避免控制器中flush、缓存头及无超时I/O,同时前端应实现指数退避重连与唯一子路径隔离。

长轮询请求在 FrankenPHP 下为何频繁断开
FrankenPHP 的默认连接超时是 60 秒,而长轮询依赖服务端挂起响应直到有新数据,一旦超过这个时限,连接会被底层 HTTP/2 或 HTTP/3 连接管理机制主动关闭,前端收到 502 Bad Gateway 或直接 net::ERR_CONNECTION_CLOSED。这不是 Symfony 代码问题,而是 FrankenPHP 的运行模型与传统 FPM 的根本差异:它基于共享内存和协程,但对“长时间阻塞”的容忍度更低。
如何调整 FrankenPHP 配置延长长轮询存活时间
必须显式覆盖默认超时,且需同时修改两处:
-
frankenphp.router.timeout:控制路由层最大等待时间,建议设为300(秒) -
frankenphp.worker.timeout:控制 PHP Worker 执行上限,也需同步设为300,否则路由层放行后 worker 仍会中断
配置位置在 frankenphp.yaml 或通过环境变量注入(如 FRANKENPHP_ROUTER_TIMEOUT=300)。注意:这两个值必须一致,否则会出现“刚进路由就被 worker 杀掉”的错位断连。
Symfony 控制器中避免隐式超时触发
即使 FrankenPHP 配置到位,Symfony 默认的 Response 构造或 StreamedResponse 使用不当仍会导致提前终止:
立即学习“PHP免费学习笔记(深入)”;
- 不要在控制器里调用
flush()或ob_flush()—— FrankenPHP 不支持 PHP 输出缓冲的逐块刷新语义 - 禁用
Response::setPublic()和缓存头,长轮询响应必须是私有、不可缓存的 - 用
StreamedResponse时,确保回调函数内不包含任何可能抛出异常或超时的 I/O 操作(如未加超时的 Redisbrpop) - 推荐使用带超时的阻塞读取,例如:
$redis->brpop(['notifications:'.$userId], 280)(留 20 秒余量给网络传输)
客户端重连逻辑必须带退避策略
即便服务端撑住了,网络抖动或负载波动仍会导致偶发断连。前端不能简单 new WebSocket() 那样重试,而要实现指数退避:
- 首次失败后延迟 1s 重试,第二次 2s,第三次 4s,上限封顶到 30s
- 每次重试前检查
document.hidden,页面不可见时暂停轮询,避免后台标签页堆积请求 - 在请求 URL 中加入单调递增的
seq参数(如?seq=12345),服务端可据此跳过已推送过的消息
最易被忽略的是:FrankenPHP 的连接复用机制会让多个长轮询请求共用一个 HTTP/2 流,一旦某个请求因超时关闭,可能意外影响同域名下其他请求的流状态——所以务必确保每个长轮询请求使用唯一子路径(如 /poll/user/123 而非统一的 /poll),避免流级干扰。



















