frankenphp 不支持原生消息推送,需搭配独立 swoole websocket 服务实现;业务 api 由 frankenphp 处理,推送通过 redis pub/sub 通信,websocket 服务负责长连接与广播,并完成身份校验、心跳、重连等闭环逻辑。

FrankenPHP 本身不直接支持长连接或消息推送,它本质是 PHP 运行时 + Swoole 驱动的 HTTP 服务器,没有内置 WebSocket 或 Server-Sent Events(SSE)服务端逻辑。要实现「消息同步推给 App 端」,必须自己补全推送通道——最常用、最可控的方式是走 WebSocket。
为什么不能只靠 FrankenPHP 的 handle 或 route 做推送
FrankenPHP 的 PHP 脚本默认以 HTTP 请求-响应模型运行,一次请求结束后连接就关闭。App 端无法持续监听,也就谈不上“同步推”。你可能会看到 Connection: keep-alive 或尝试 flush() + ob_flush(),但这在 FrankenPHP 中不可靠:底层基于 Swoole 的协程 HTTP server 对流式响应支持有限,且移动端网络(尤其 iOS)会主动断开空闲长连接。
- HTTP 流(如 SSE)在 FrankenPHP v1.1+ 有基础支持,但需手动管理连接生命周期,App 端兼容性差(iOS Safari 对 SSE 的重连逻辑不健壮)
-
file_get_contents()或轮询只是伪实时,延迟高、耗电、易被限流 - FrankenPHP 没有类似 Laravel Echo Server 或 Pusher 的内置广播驱动
推荐方案:用 Swoole WebSocket Server 单独承载推送通道
FrankenPHP 负责业务 API(如发消息、查状态),另起一个独立的 Swoole WebSocket 服务负责维持长连接和广播。两者通过 Redis Pub/Sub 或本地 socket 通信。这是目前最稳定、可横向扩展的做法。
- WebSocket 服务用纯 PHP 写,无需额外语言;Swoole 扩展已随 FrankenPHP 一起安装(
swoole.enable_coroutine=1默认开启) - App 端(iOS/Android)用标准
WebSocket客户端连接ws://your-domain.com:9502,比 HTTP 流更可靠 - 关键点:WebSocket 服务必须与 FrankenPHP 共享用户身份上下文——建议登录后下发一个短期有效的
token,由 WebSocket 服务调用 FrankenPHP 的/api/auth/verify接口校验(用curl_init()同机直连,避免公网绕行) - 示例启动命令:
php websocket-server.php
,其中websocket-server.php监听0.0.0.0:9502,并订阅 Redis 频道push:topic:order
如何让 FrankenPHP 主动触发推送(比如用户下单后推通知)
不要在 PHP-FPM 风格的控制器里直接 new Swoole\WebSocket\Server —— FrankenPHP 不允许运行时创建新 server 实例,会报 Operation not permitted。
- 正确做法:FrankenPHP 收到订单请求后,向 Redis 发布消息:
$redis->publish('push:topic:order', json_encode(['user_id' => 123, 'msg' => '订单已支付'])); - 你的独立 WebSocket 服务早已
$redis->subscribe(['push:topic:order']),收到后遍历在线连接,筛选出目标user_id对应的$fd,再调用$server->push($fd, $payload) - 别漏掉连接失效清理:WebSocket 服务需监听
onClose事件,从用户fd映射表中删除记录;也可加心跳包(onMessage检查 ping/pong)
App 端接入要注意的真实细节
很多团队卡在最后一步:App 收不到推送,其实是客户端没处理好异常路径。
- iOS WKWebView 默认禁用 WebSocket,需在
Info.plist加NSAppTransportSecurity配置,或改用原生URLSessionWebSocketTask - Android OkHttp 3.14+ 支持 WebSocket,但必须手动设置
pingInterval(建议 30s),否则弱网下连接静默断开 - 不要依赖
onopen后立刻发消息——App 启动时可能先连上 WS,再调 API 登录,此时服务端还不知道这个fd属于谁;务必设计「绑定 token」握手流程(例如连接后立刻发{"type":"bind","token":"abc123"}) - 消息体建议固定字段:
{"id":"uuid","event":"order_paid","data":{...},"ts":1733318888},方便 App 端做去重和离线缓存
真正难的不是写几行 $server->push(),而是把用户身份、连接生命周期、网络中断恢复、消息可达性这四件事串成闭环。FrankenPHP 只解决了一半——剩下那一半得靠你亲手把 Redis、Swoole WebSocket、App 心跳、token 绑定全拧在一起。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











