php原生不支持长连接,必须依赖swoole等常驻进程扩展;fpm模式下无法维持socket连接,websocket服务只能在cli模式下通过事件循环长期驻留运行。

PHP 原生不支持长连接,必须依赖 Swoole 这类常驻进程扩展才能真正跑起 WebSocket 服务;FPM 模式下任何“WebSocket”方案都是伪实时,本质仍是轮询或 Server-Sent Events(SSE)。
为什么不能直接在 Laravel/Symfony 的 FPM 上启 WebSocket 服务
因为 PHP-FPM 是请求-响应生命周期模型:每个 HTTP 请求处理完就释放所有资源,无法维持 socket 连接。哪怕你用 socket_create 手动建连接,进程一结束,连接就断。
-
Swoole\WebSocket\Server必须运行在 CLI 模式下,靠事件循环长期驻留内存 - Laravel 的
php artisan serve或 Nginx + PHP-FPM 组合,压根没有「监听端口 + 管理连接」的能力 - 所谓“集成”,实际是两个独立进程:FPM 处理业务逻辑(如登录、消息存库),Swoole 进程单独跑 WebSocket 服务,二者靠
Redis pub/sub或Unix socket通信
Swoole WebSocket 服务启动时的常见报错与修复
最典型的是 Failed to listen on port: 9501, Error: Address already in use,说明端口被占;或者 Segmentation fault,多因 Swoole 版本与 PHP 不兼容。
- 检查端口占用:
lsof -i :9501或netstat -tuln | grep 9501,杀掉残留进程 - Swoole 安装后务必验证:
php --ri swoole,确认websocket => enabled - PHP 8.2+ 用户注意:Swoole 5.0.3 之前版本存在协程调度 bug,建议升到
5.1.0+ - 别在
onMessage回调里直接调file_get_contents或curl_exec—— 会阻塞整个 event loop,改用Swoole\Coroutine\Http\Client
如何让客户端稳定重连而不压垮服务端
前端用原生 WebSocket API 时,onclose 触发后立刻 new WebSocket() 是最危险的做法,容易引发雪崩。
- 必须实现指数退避:
1s → 3s → 7s → 15s,而非固定间隔 - 重连前加随机抖动(±300ms),避免集群内大量客户端同步重试
- 服务端配合做连接频控:在
onOpen中查 Redis 记录该 IP 5 分钟内连接次数,超限则$fd->close() - 心跳不能只靠浏览器自动 ping/pong —— 客户端需主动发
{"type":"ping"},服务端在onMessage中识别并回{"type":"pong"},超时未收到则$server->close($fd)
广播消息性能瓶颈在哪,怎么绕过
直接遍历 $server->connections 并逐个 push,在万级连接时 CPU 和网络 IO 都会打满。
- 不要用
foreach ($server->connections as $fd)广播 —— connections 是迭代器,每次遍历都触发全量扫描 - 改用
$server->task()把广播任务投递到 task 进程,由它异步分片推送(例如每批 200 个 fd) - 更优解是用 Redis Pub/Sub:客户端发消息 → 写 Redis Channel → Swoole 的
Redis->subscribe()监听 → 收到后只推给目标用户(非全网) - 如果必须全网广播,启用
websocket.compress = On(Swoole 5.0+),对文本消息自动 gzip
真正的难点不在写通连接,而在于连接数上去之后,内存泄漏、协程上下文污染、Redis 连接池耗尽这些隐性问题才会暴露——建议上线前用 ab 或 wrk 模拟 5000+ 连接持续 30 分钟,观察 memory_get_usage() 和 swoole_server->stats() 输出。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











