frankenphp 不提供 websocket 自动重连能力,重连逻辑必须由开发者在应用层或前端 js 中显式实现;其本身仅作为运行时桥接 http/https/http3 流量,不感知连接状态、不触发 onclose、不参与心跳与凭证管理,所有连接生命周期控制需手动精准对齐代理超时、服务端保活、客户端心跳及 token 有效期。

FrankenPHP 本身不提供 WebSocket 自动重连能力——它只是将 PHP 请求生命周期与 HTTP/HTTPS/HTTP3 流量桥接的运行时,WebSocket 连接的实际建立、维护、断开响应全部落在 PHP 应用层(如 Swoole、ReactPHP 或自定义 socket 服务)或前端 JS 中。重连逻辑必须由你显式编码实现,不能依赖 FrankenPHP 自动触发。
为什么 onclose 不总被触发,且不能只靠它重连
WebSocket 断开常表现为 readyState === 1 但收不到消息、发不出数据,底层 TCP 已断,但浏览器/客户端未收到关闭帧,onclose 延迟数秒甚至永不触发。FrankenPHP 作为反向代理或嵌入式运行时,对此无感知,也不会帮你补发事件。
- 仅监听
onclose会漏掉“假连接”(ghost connection),导致消息静默丢失 - 若服务端是 FrankenPHP + Swoole 启动的 WebSocket 服务,
onClose回调在 PHP 层可捕获,但客户端 JS 仍需独立心跳验证 - 移动端或 NAT 环境下,连接可能被中间设备静默回收,此时既无
onclose,也无onerror
必须加业务层心跳,且间隔要严守服务端保活阈值
FrankenPHP 通常配合 Nginx 或 Caddy 做前置代理,而这些代理默认 proxy_read_timeout 是 60 秒。如果你的心跳间隔 > 60 秒,连接大概率被代理强制切断——这不是 FrankenPHP 的问题,而是配置错配。
- 客户端 JS 心跳建议设为 25–30 秒发送
{"type":"ping"},服务端收到后立即回{"type":"pong"} - 启动一个
setTimeout计时器(比如 5 秒),若未收到pong就主动调用ws.close(4999, "no_pong") - 服务端(如 Swoole)需设置
$server->set(['heartbeat_idle_time' => 60, 'heartbeat_check_interval' => 30]),确保与前端心跳对齐 - FrankenPHP 自身不参与心跳逻辑,但它转发的请求必须能抵达你的 PHP WebSocket 服务,所以检查
fastcgi_read_timeout或http3_idle_timeout同样关键
重连要用指数退避 + 随机抖动,别写 setTimeout(connect, 1000)
固定 1 秒重连在 FrankenPHP 部署多个实例或用户量上升时,极易引发服务端连接风暴。Nginx 的 limit_conn 或 Swoole 的 max_connection 会被瞬间打满。
- 基础延迟:从
1000ms 开始,每次失败翻倍:Math.min(1000 * Math.pow(2, retryCount), 30000) - 叠加 ±30% 随机抖动:
delay += Math.random() * delay * 0.3 - 重试上限设为 10 次,超限后停止自动重连,改由用户手动刷新或显示“网络异常,请重试”
-
retryCount必须在onopen中清零;否则跨次重连会沿用旧值,第 11 次就直接放弃 - 每次新建
WebSocket前,务必执行wsRef?.close()和clearInterval(pingInterval),否则残留定时器和连接会堆积
凭证过期是重连失败最隐蔽的根源
FrankenPHP 常用于托管带鉴权的 WebSocket 接口(如 wss://app.example.com/ws?token=xxx)。token 一般有 5–30 分钟有效期。一旦连接中断超过 token 有效期,重连时复用旧 URL 会直接返回 401 或被服务端拒绝,形成“断开 → 重连 → 失败 → 再断开”的死循环。
- 重连前必须重新获取 token:走一次
/api/auth/refresh或从 localStorage 读取并校验时间戳 - 不要把 token 存在 URL 参数里长期复用;改用
Authorization: Bearer xxx请求头(需服务端支持) - 如果使用 Swoole 实现服务端,注意
$connection->getHeaders()['authorization']能否正确解析 bearer token——FrankenPHP 不修改请求头,但某些代理可能 strip 它 - 生产环境 80% 的“重连总失败”,查到最后都是 token 过期 + 心跳间隔 > 代理 timeout 的组合问题
真正难的不是写几行 setTimeout,而是让心跳、重连、凭证、代理配置四者节奏完全对齐。FrankenPHP 只是通道,它不替你做状态判断,也不替你管连接生命周期——这些都得你亲手拧紧每一颗螺丝。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











