WebSocket服务端必须用CLI常驻进程运行,不可嵌入PHP-FPM;需实现MessageComponentInterface接口,配置Nginx透传Upgrade头,限制消息大小并处理并发安全。

WebSocket 服务端必须独立运行,不能塞进 PHP-FPM
PHP 默认的 Web 服务器(比如 Apache + mod_php 或 Nginx + PHP-FPM)是无状态、请求-响应模型,根本不支持长连接维持。Ratchet 的 WebSocketServer 是一个常驻内存的 CLI 进程,靠 ReactPHP 的事件循环驱动,和你平时写的 index.php 完全不是一回事。
常见错误现象:
— 把 Ratchet 启动代码放进 Web 路由里,页面一刷就断,连不上;
— 用 php server.php 启动后没加 & 或进程管理,SSH 断开就挂了;
— 误以为它能自动热重载,改完代码不重启进程,行为诡异。
- 必须用命令行启动:例如
php bin/server.php - 生产环境务必配合
supervisord或systemd管理进程生命周期 - 别监听
127.0.0.1:8080就完事——前端要连,得确保端口对外可访问,且反向代理(如 Nginx)需透传 WebSocket 协议(关键头:Upgrade和Connection: upgrade)
Ratchet 的 onOpen/onMessage/onClose 不是“回调”,而是对象方法调用
很多人照着文档写个匿名函数塞进 onOpen,结果发现收不到消息或连接一建就崩。Ratchet 要求你实现 MessageComponentInterface 接口,所有事件都落在类实例的方法上,不是自由传函数。
典型错误:
— 直接在 new WsServer(new YourHandler()) 里传闭包,报错 Argument 1 passed to Ratchet\Server\IoServer::__construct() must implement interface Ratchet\MessageComponentInterface;
— 在 onMessage 里用 $this->clients 但没声明属性,PHP 7.4+ 会 Notice;
— 忘记给 $conn 加类型提示(ConnectionInterface),IDE 提示弱,容易传错对象。
立即学习“PHP免费学习笔记(深入)”;
- 必须定义一个类,
implements MessageComponentInterface -
onOpen(ConnectionInterface $conn)中,$conn->resourceId是唯一 ID,不是$conn->id - 广播时别直接遍历
$this->clients并调用$conn->send()—— 要先检查$conn->isConnected(),否则已断开的连接会抛异常
客户端连不上?先确认 HTTP 升级是否被中间件拦住
WebSocket 连接始于一次 HTTP 请求,带 Upgrade: websocket 头。很多环境默认过滤或重写这类头,导致握手失败,浏览器控制台报错 WebSocket connection to 'ws://...' failed,但服务端日志可能完全静默。
使用场景:
— 本地开发用 ws://localhost:8080 没问题,部署到 Nginx 后变成 ws://yourdomain.com 就连不上;
— HTTPS 站点强制要求 wss://,用 ws:// 会被浏览器直接拒绝(Mixed Content)。
- Nginx 配置里必须显式透传升级头:
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade"; - 如果用了 Cloudflare,免费版默认关闭 WebSocket 支持,需在仪表盘开启「WebSockets」开关
- 调试时用
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" http://localhost:8080看响应头有没有HTTP/1.1 101 Switching Protocols
消息体过大或频率过高,Ratchet 默认会直接断连
Ratchet 基于 ReactPHP,默认对单条 WebSocket 帧有 1MB 大小限制,且没有内置流式接收机制。发个 2MB 的 JSON 或高频发送(如每 50ms 一条),服务端会静默关闭连接,客户端只看到 close 事件,无具体错误码。
性能影响:
— 每次 $conn->send($bigData) 都会触发一次完整内存拷贝,大消息多连几个用户就 OOM;
— 默认未启用 permessage-deflate 扩展,压缩得自己处理。
- 限制单条消息大小:在
onMessage开头加if (strlen($msg) > 512 * 1024) { $conn->close(4001, 'Message too large'); return; } - 高频场景改用“合并推送”:缓存几条消息,用
React\EventLoop\Timer\IntervalTimer每 100ms 批量 send,而不是每条都立刻发 - 若真需要大文件传输,别走 WebSocket —— 改用 HTTP 分片上传 + WebSocket 仅作状态通知
最常被忽略的一点:Ratchet 的 ConnectionInterface 对象不是线程安全的,多个事件回调(比如同时触发 onMessage 和 onClose)可能并发修改同一属性,该加锁的地方得自己用 flock 或 pcntl_fork 信号协调,别指望框架兜底。



















